diff --git a/.agents/skills/doc-maintenance/SKILL.md b/.agents/skills/doc-maintenance/SKILL.md index 839786bc826..263cd792358 100644 --- a/.agents/skills/doc-maintenance/SKILL.md +++ b/.agents/skills/doc-maintenance/SKILL.md @@ -1,7 +1,7 @@ --- name: doc-maintenance description: > - Audit README, SPEC, and PRODUCT docs against recent git history for drift and + Audit README and PRODUCT docs against recent git history for drift and make minimal PR-ready edits. Use when asked to review docs for accuracy, after major feature merges, or on a schedule. --- @@ -15,14 +15,13 @@ Detect documentation drift and fix it via PR — no rewrites, no churn. - Periodic doc review (e.g. weekly or after releases) - After major feature merges - When asked "are our docs up to date?" -- When asked to audit README / SPEC / PRODUCT accuracy +- When asked to audit README / PRODUCT accuracy ## Target Documents | Document | Path | What matters | |----------|------|-------------| | README | `README.md` | Features table, roadmap, quickstart, "what is" accuracy, "works with" table | -| SPEC | `doc/SPEC.md` | No false "not supported" claims, major model/schema accuracy | | PRODUCT | `doc/PRODUCT.md` | Core concepts, feature list, principles accuracy | Out of scope: DEVELOPING.md, DATABASE.md, CLI.md, doc/plans/, skill files, @@ -122,7 +121,7 @@ Apply **only** the edits needed to fix drift. Rules: Commit the changes and open a PR: ```bash -git add README.md doc/SPEC.md doc/PRODUCT.md .doc-review-cursor +git add README.md doc/PRODUCT.md .doc-review-cursor git commit -m "docs: update documentation for accuracy - [list each fix briefly] @@ -184,7 +183,7 @@ were needed, commit the cursor update to the current branch. - If adding a feature mention, match the format of surrounding entries (e.g. if features are in a table, add a table row) - Keep README changes especially minimal — it shouldn't churn often -- For SPEC/PRODUCT, prefer updating existing statements over adding new ones +- For PRODUCT, prefer updating existing statements over adding new ones (e.g. change "not supported in V1" to "supported via X" rather than adding a new section) diff --git a/.agents/skills/doc-maintenance/references/audit-checklist.md b/.agents/skills/doc-maintenance/references/audit-checklist.md index 9c13a437d54..66373334ee5 100644 --- a/.agents/skills/doc-maintenance/references/audit-checklist.md +++ b/.agents/skills/doc-maintenance/references/audit-checklist.md @@ -46,29 +46,6 @@ against the change summary from git history. - [ ] Commands are accurate (`pnpm dev`, `pnpm build`, etc.) - [ ] Link to DEVELOPING.md is correct -## doc/SPEC.md - -### Company Model -- [ ] Fields match current schema -- [ ] Governance model description is accurate - -### Agent Model -- [ ] Adapter types match what's actually supported -- [ ] Agent configuration description is accurate -- [ ] No features described as "not supported" or "not V1" that shipped - -### Task Model -- [ ] Task hierarchy description is accurate -- [ ] Status values match current implementation - -### Extensions / Plugins -- [ ] If plugins are shipped, no "not in V1" or "future" language -- [ ] Plugin model description matches implementation - -### Open Questions -- [ ] Resolved questions removed or updated -- [ ] No "TBD" items that have been decided - ## doc/PRODUCT.md ### Core Concepts diff --git a/.agents/skills/doc-maintenance/references/section-map.md b/.agents/skills/doc-maintenance/references/section-map.md index 4ec64f83875..b413194c764 100644 --- a/.agents/skills/doc-maintenance/references/section-map.md +++ b/.agents/skills/doc-maintenance/references/section-map.md @@ -3,20 +3,20 @@ Maps feature areas to specific document sections so the skill knows where to look when a feature ships or changes. -| Feature Area | README Section | SPEC Section | PRODUCT Section | -|-------------|---------------|-------------|----------------| -| Plugins / Extensions | Features table, Roadmap | Extensions, Agent Model | Core Concepts | -| Adapters (new runtimes) | "Works with" table, FAQ | Agent Model, Agent Configuration | Employees & Agents, Agent Execution | -| Governance / Approvals | Features table, "Why special" | Board Governance, Board Approval Gates | Principles | -| Budget / Cost Control | Features table, "Why special" | Budget Delegation | Company (revenue & expenses) | -| Task Management | Features table | Task Model | Task Management | -| Org Chart / Hierarchy | Features table | Agent Model (reporting) | Employees & Agents | -| Multi-Company | Features table, FAQ | Company Model | Company | -| Heartbeats | Features table, FAQ | Agent Execution | Agent Execution | -| CLI Commands | Development section | — | — | -| Onboarding / Quickstart | Quickstart, FAQ | — | User Flow | -| Skills / Skill Injection | "Why special" | — | — | -| Company Templates | "Why special", Roadmap (ClipMart) | — | — | -| Mobile / UI | Features table | — | — | -| Project Archiving | — | — | — | -| OpenClaw Integration | "Works with" table, FAQ | Agent Model | Agent Execution | +| Feature Area | README Section | PRODUCT Section | +|-------------|---------------|----------------| +| Plugins / Extensions | Features table, Roadmap | Core Concepts | +| Adapters (new runtimes) | "Works with" table, FAQ | Employees & Agents, Agent Execution | +| Governance / Approvals | Features table, "Why special" | Principles | +| Budget / Cost Control | Features table, "Why special" | Company (revenue & expenses) | +| Task Management | Features table | Task Management | +| Org Chart / Hierarchy | Features table | Employees & Agents | +| Multi-Company | Features table, FAQ | Company | +| Heartbeats | Features table, FAQ | Agent Execution | +| CLI Commands | Development section | — | +| Onboarding / Quickstart | Quickstart, FAQ | User Flow | +| Skills / Skill Injection | "Why special" | — | +| Company Templates | "Why special", Roadmap (ClipMart) | — | +| Mobile / UI | Features table | — | +| Project Archiving | — | — | +| OpenClaw Integration | "Works with" table, FAQ | Agent Execution | diff --git a/.devin/wiki.json b/.devin/wiki.json index 4bd4b9457e5..4aaba2cc90d 100644 --- a/.devin/wiki.json +++ b/.devin/wiki.json @@ -4,7 +4,7 @@ "content": "Audience and outcome: write a guided architecture handbook for contributors and coding agents. Help a reader understand the control plane, trace a task through the code, choose the right place to change behavior, and find the tests that protect it. Explain the problem and mental model before introducing symbols. Prefer a concrete example and a short causal explanation to exhaustive class, endpoint, package, or configuration inventories. Use plain language and define abbreviations on first use." }, { - "content": "Evidence and document authority: use current implementation, schemas, configuration, public exports, and tests to establish what ships in the indexed revision. Read `doc/GOAL.md` and `doc/PRODUCT.md` for intent and `doc/SPEC-implementation.md` for the V1 policy contract; `doc/SPEC.md` provides longer-horizon context. Contributor instructions such as `AGENTS.md` are not implementation evidence. Explain material disagreements between code and policy instead of silently treating either as proof of the other. Plans, historical snapshots, proposed ADRs, prototypes, fixtures, and roadmap entries do not establish shipping behavior. Verify their claims against production wiring and distinguish implemented, gated/experimental, and proposed behavior." + "content": "Evidence and document authority: use current implementation, schemas, configuration, public exports, and tests to establish what ships in the indexed revision. Read `doc/GOAL.md` and `doc/PRODUCT.md` for intent; the focused docs under `doc/` (for example `doc/execution-semantics.md`) give background on policy. Contributor instructions such as `AGENTS.md` are not implementation evidence. Explain material disagreements between code and policy instead of silently treating either as proof of the other. Plans, historical snapshots, proposed ADRs, prototypes, fixtures, and roadmap entries do not establish shipping behavior. Verify their claims against production wiring and distinguish implemented, gated/experimental, and proposed behavior." }, { "content": "Page contract: each subsystem page should explain the problem it solves, what it owns, its inputs and outputs, authoritative persisted state, and its boundaries with neighboring systems. Trace one representative flow through actual entry points. Include a compact 'Where to change it' map linking responsibilities to source, relevant regression tests with the behavior they protect, and practical investigation steps for characteristic failures. Cite the code that performs a decision or effect rather than an import list or unrelated file. A passing unit fixture does not establish live provider support. Parent pages should offer reading routes, boundary maps, or diagnostic routing instead of repeating all their children." @@ -34,7 +34,7 @@ "content": "Introduce the company control plane through one running example: an operator asks an agent to produce a report, the agent works under company policy, and the operator receives an inspectable result. Explain the roles of the board, company, agent, task, execution service, and work product. Offer three linked reading routes: understand the system, make a first change, and diagnose work that stopped. Explain which chapter answers which question and link to official quickstart/operator docs. Keep this page approachable; leave architecture detail, entity mapping, and the complete code trace to its three children." }, { - "content": "Source anchors: `README.md`, `doc/GOAL.md`, `doc/PRODUCT.md`, `doc/SPEC-implementation.md`, `docs/start/what-is-paperclip.md`, `docs/start/core-concepts.md`, `docs/docs.json`. Verify the example's persisted task/output concepts against `packages/db/src/schema/issues.ts`, `server/src/services/work-products.ts`, and `server/src/__tests__/work-products.test.ts`; do not imply the example is an executed demo." + "content": "Source anchors: `README.md`, `doc/GOAL.md`, `doc/PRODUCT.md`, `docs/start/what-is-paperclip.md`, `docs/start/core-concepts.md`, `docs/docs.json`. Verify the example's persisted task/output concepts against `packages/db/src/schema/issues.ts`, `server/src/services/work-products.ts`, and `server/src/__tests__/work-products.test.ts`; do not imply the example is an executed demo." } ] }, @@ -60,7 +60,7 @@ "content": "Use a concept-to-schema/service table for company, agent/org tree, goal, project, task/issue, wake, run, session, approval/interaction, and work product. Explain company scope and documented instance-level exceptions without asserting every table has identical tenancy. Distinguish assignment from execution ownership, parent/child structure from blockers, and business goals from provider session goals. Give concrete examples of atomic checkout, company-boundary rejection, and a durable wait. Explain where these invariants are enforced and tested; leave full state transitions, permissions, and database operations to their owning pages." }, { - "content": "Source anchors: `doc/SPEC-implementation.md`, `packages/db/src/schema/index.ts`, `packages/db/src/schema/issues.ts`, `packages/db/src/schema/agents.ts`, `packages/db/src/schema/heartbeat_runs.ts`, `packages/db/src/schema/agent_task_sessions.ts`, `packages/shared/src/constants.ts`, `server/src/services/issues.ts`. Regression anchors: `server/src/__tests__/authz-company-access.test.ts`, `server/src/__tests__/issues-service.test.ts`, `server/src/__tests__/heartbeat-auto-checkout.test.ts`." + "content": "Source anchors: `doc/execution-semantics.md`, `packages/db/src/schema/index.ts`, `packages/db/src/schema/issues.ts`, `packages/db/src/schema/agents.ts`, `packages/db/src/schema/heartbeat_runs.ts`, `packages/db/src/schema/agent_task_sessions.ts`, `packages/shared/src/constants.ts`, `server/src/services/issues.ts`. Regression anchors: `server/src/__tests__/authz-company-access.test.ts`, `server/src/__tests__/issues-service.test.ts`, `server/src/__tests__/heartbeat-auto-checkout.test.ts`." } ] }, @@ -367,7 +367,7 @@ "content": "Trace representative board/session and agent-key requests through authentication, actor derivation, company access, resource lookup, action policy, and mutation attribution. Verify current support for other credential types rather than presenting a fixed inventory. Explain deployment exposure versus company visibility, resource membership versus permission grants, and default-open versus explicitly restricted operations. Identify legitimate instance-level exceptions and low-trust containment without confusing them with task privacy. Show cross-company and existence-oracle rejection tests. Distinguish core enforcement from optional administrative UI and provider permission settings; link tool grants, secrets, and budgets to their owning pages." }, { - "content": "Source anchors: `doc/DEPLOYMENT-MODES.md`, `doc/SPEC-implementation.md`, `doc/LOW-TRUST-PRESETS.md`, `server/src/middleware/auth.ts`, `server/src/routes/authz.ts`, `server/src/services/access.ts`, `server/src/services/authorization.ts`, `packages/shared/src/constants.ts`, `packages/db/src/schema/principal_permission_grants.ts`. Regression anchors: `server/src/__tests__/authz-company-access.test.ts`, `server/src/__tests__/authz-existence-oracle-guard.test.ts`, `server/src/__tests__/authz-secret-context.test.ts`." + "content": "Source anchors: `doc/DEPLOYMENT-MODES.md`, `doc/agent-permission-defaults.md`, `doc/LOW-TRUST-PRESETS.md`, `server/src/middleware/auth.ts`, `server/src/routes/authz.ts`, `server/src/services/access.ts`, `server/src/services/authorization.ts`, `packages/shared/src/constants.ts`, `packages/db/src/schema/principal_permission_grants.ts`. Regression anchors: `server/src/__tests__/authz-company-access.test.ts`, `server/src/__tests__/authz-existence-oracle-guard.test.ts`, `server/src/__tests__/authz-secret-context.test.ts`." } ] }, diff --git a/AGENTS.md b/AGENTS.md index 58331074ec7..08c0fafc168 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ The invariants and rules below record what has been decided so far. If you think Paperclip is a control plane for AI-agent companies. The code and its tests are the source of truth for current behavior. -`doc/SPEC-implementation.md` and `doc/SPEC.md` are background. They can be out of date, so do not treat them as requirements when they disagree with the code or with the task. +The focused docs under `doc/` are background. They can be out of date, so do not treat them as requirements when they disagree with the code or with the task. ## 2. Read This First diff --git a/doc/AGENTCOMPANIES_SPEC_INVENTORY.md b/doc/AGENTCOMPANIES_SPEC_INVENTORY.md index 99de314d052..c5ba31e05fc 100644 --- a/doc/AGENTCOMPANIES_SPEC_INVENTORY.md +++ b/doc/AGENTCOMPANIES_SPEC_INVENTORY.md @@ -16,7 +16,6 @@ Use it when you need to: |---|---| | `docs/companies/companies-spec.md` | **Normative spec** — defines the markdown-first package format (COMPANY.md, TEAM.md, AGENTS.md, PROJECT.md, TASK.md, SKILL.md), reserved files, frontmatter schemas, and vendor extension conventions (`.paperclip.yaml`). | | `doc/plans/2026-03-13-company-import-export-v2.md` | Implementation plan for the markdown-first package model cutover — phases, API changes, UI plan, and rollout strategy. | -| `doc/SPEC-implementation.md` | V1 implementation contract; references the portability system and `.paperclip.yaml` sidecar format. | | `docs/specs/cliphub-plan.md` | Earlier blueprint bundle plan; partially superseded by the markdown-first spec (noted in the v2 plan). | | `doc/plans/2026-02-16-module-system.md` | Module system plan; JSON-only company template sections superseded by the markdown-first model. | | `doc/plans/2026-03-14-skills-ui-product-plan.md` | Skills UI plan; references portable skill files and `.paperclip.yaml`. | diff --git a/doc/CLIPHUB.md b/doc/CLIPHUB.md index 0d70d9db69b..5b48137e681 100644 --- a/doc/CLIPHUB.md +++ b/doc/CLIPHUB.md @@ -8,7 +8,7 @@ ClipHub is the public registry where people share, discover, and download Paperc ## What It Is -ClipHub is to Paperclip what a package registry is to a programming language. Paperclip already supports exportable org configs (see [SPEC.md](./SPEC.md) §2). ClipHub is the public directory where those exports live. +ClipHub is to Paperclip what a package registry is to a programming language. Paperclip already supports exportable org configs (see the [company package spec](../docs/companies/companies-spec.md)). ClipHub is the public directory where those exports live. A user builds a working company in Paperclip — a dev shop, a marketing agency, a research lab, a content studio — exports the template, and publishes it to ClipHub. Anyone can browse, search, download, and spin up that company on their own Paperclip instance. diff --git a/doc/DEPLOYMENT-MODES.md b/doc/DEPLOYMENT-MODES.md index d0660cbd5d7..32aa8488920 100644 --- a/doc/DEPLOYMENT-MODES.md +++ b/doc/DEPLOYMENT-MODES.md @@ -226,6 +226,5 @@ future public-hosted setup design explicitly changes this policy. ## 11. Relationship to Other Docs - implementation plan: `doc/plans/2026-02-23-deployment-auth-mode-consolidation.md` -- V1 contract: `doc/SPEC-implementation.md` - operator workflows: `doc/DEVELOPING.md` and `doc/CLI.md` - invite/join state map: `doc/spec/invite-flow.md` diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 97a8fd93214..dc8d738be44 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -2374,8 +2374,7 @@ for storage, import, retention, and lifecycle regression checks. These checks do not qualify a deployed Runner/provider or a zero-outage webserver deploy. Keep the existing persistent volume until the other instance data paths and deploy handoff are separately qualified. See the -[implementation contract](SPEC-implementation.md#experimental-native-remote-workspace-authority-2026-10-30) -and [snapshot storage contract](DATABASE.md#experimental-remote-workspace-snapshots). +[snapshot storage contract](DATABASE.md#experimental-remote-workspace-snapshots). ## Agent Artifact Uploads diff --git a/doc/PRODUCT.md b/doc/PRODUCT.md index 1090293c15a..74186bf151d 100644 --- a/doc/PRODUCT.md +++ b/doc/PRODUCT.md @@ -324,7 +324,7 @@ Canonical mode design and command expectations live in `doc/DEPLOYMENT-MODES.md` ## Further Detail -See [SPEC.md](./SPEC.md) for the full technical specification and [TASKS.md](./TASKS.md) for the task management data model. +See [TASKS.md](./TASKS.md) for the task management data model. --- diff --git a/doc/SPEC-implementation.md b/doc/SPEC-implementation.md deleted file mode 100644 index 485c8b17dc7..00000000000 --- a/doc/SPEC-implementation.md +++ /dev/null @@ -1,3027 +0,0 @@ -# Paperclip V1 Implementation Spec - -Status: Implementation contract for first release (V1) -Date: 2026-04-28 -Audience: Product, engineering, and agent-integration authors -Source inputs: `GOAL.md`, `PRODUCT.md`, `SPEC.md`, `DATABASE.md`, current monorepo code - -## 1. Document Role - -`SPEC.md` remains the long-horizon product spec. -This document is the concrete, build-ready V1 contract. -When there is a conflict, `SPEC-implementation.md` controls V1 behavior. - -## 2. V1 Outcomes - -Paperclip V1 must provide a full control-plane loop for autonomous agents: - -1. A human board creates a company and defines goals. -2. The board creates and manages agents in an org tree. -3. Agents receive and execute tasks via heartbeat invocations. -4. All work is tracked through tasks/comments with audit visibility. -5. Token/cost usage is reported and budget limits can stop work. -6. The board can intervene anywhere (pause agents/tasks, override decisions). - An effective task or ancestor pause replaces the message composer with an - amber Resume takeover. New board messages, including updates with comments, - are rejected until the hold is released. Drafts survive pause and resume. - -Success means one operator can run a small AI-native company end-to-end with clear visibility and control. - -## 3. Explicit V1 Product Decisions - -These decisions close open questions from `SPEC.md` for V1. - -| Topic | V1 Decision | -|---|---| -| Tenancy | Single-tenant deployment, multi-company data model | -| Company model | Company is first-order; all business entities are company-scoped | -| Board | Single human board operator per deployment | -| Org graph | Strict tree (`reports_to` nullable root); no multi-manager reporting | -| Visibility | Issues remain company-open by default. Opt-in `private` issues use task ACLs; deployment exposure flags remain independent of work-object privacy. | -| Communication | Tasks + comments only (no separate chat system) | -| Composer shortcuts | Task replies and new tasks submit with Enter; Shift+Enter adds a new line. The off-by-default `enableLegacyComposerSubmit` preference in Experimental settings restores Command/Ctrl+Enter submission and Enter for a new line. | -| Experimental voice transport | Speko browser voice can create or resume a task through the existing chat-connection delivery/publication pipeline. The selected Paperclip agent retains execution authority. Phone support remains gated on live continuation qualification. | -| Task ownership | Single assignee; atomic checkout required for `in_progress` transition | -| Task watchdogs | A task watchdog is an explicitly configured, issue-subtree-scoped verification and recovery capacity. It may restore live task paths inside the watched subtree; for issue-thread interaction resolution it is an ordinary agent subject to the same audience and containment checks, not board authority, active-run output monitoring, or general liveness recovery. | -| Recovery | Liveness/watchdog recovery preserves explicit ownership: continue interrupted local conversations with bounded fresh turns and preserved history, never replay tool calls automatically; retain native ownership and real execution gates; preserve verified stop evidence and reconsider saved post-stop user messages after cleanup; otherwise open visible source-scoped recovery actions by default, use issue-backed recovery only for independent repair work, or require human escalation (see `doc/execution-semantics.md`) | -| Agent adapters | Built-in `process`, `http`, local CLI/session adapters, and OpenClaw gateway support; external adapters can also be loaded through the adapter plugin flow | -| Plugin framework | Local/self-hosted early plugin runtime is in scope; cloud marketplace and packaged public distribution remain out of scope | -| Auth | Mode-dependent human auth (`local_trusted` implicit board in current code; authenticated mode uses sessions), API keys for agents | -| Budget period | Monthly UTC calendar window | -| Budget enforcement | Best-effort admission, soft alerts and asynchronous threshold auto-pause | -| Deployment modes | Canonical model is `local_trusted` + `authenticated` with `private/public` exposure policy (see `doc/DEPLOYMENT-MODES.md`) | - -Low-trust agent presets are containment controls for hostile automated work, not -general project or issue privacy controls. The core preset resolver contract is -documented in `doc/LOW-TRUST-PRESETS.md`. - -## 4. Current Baseline (Repo Snapshot) - -As of 2026-02-17, the repo already includes: - -- Node + TypeScript backend with REST CRUD for `agents`, `projects`, `goals`, `issues`, `activity` -- React UI pages for dashboard/agents/projects/goals/issues lists -- PostgreSQL schema via Drizzle with embedded PostgreSQL fallback when `DATABASE_URL` is unset - -V1 implementation extends this baseline into a company-centric, governance-aware control plane. - -## 5. V1 Scope - -## 5.1 In Scope - -Company administrators may opt in to native Codex and Claude Code logs/traces -through [company agent observability](agent-observability.md), using a single -company destination and encrypted credentials. This is separate from first-party -Telemetry and infrastructure tracing. - - -- Company lifecycle (create/list/get/update/archive) -- Goal hierarchy linked to company mission -- Agent lifecycle with org structure and adapter configuration -- Task lifecycle with parent/child hierarchy and comments -- Atomic task checkout and explicit task status transitions -- Board approvals for hires and CEO strategy proposal -- Heartbeat invocation, status tracking, and cancellation -- Cost event ingestion and rollups (agent/task/project/company) -- Budget settings and hard-stop enforcement -- Board web UI for dashboard, org chart, tasks, agents, approvals, costs -- Agent-facing API contract (task read/write, heartbeat report, cost report) -- Auditable activity log for all mutating actions - -## 5.2 Out of Scope (V1) - -- Cloud-grade plugin marketplace/distribution beyond the local/self-hosted plugin runtime -- Revenue/expense accounting beyond model/token costs -- Knowledge base subsystem -- Public marketplace (ClipHub) -- Multi-board governance (multiple board UIs for a single company) -- Automatic self-healing orchestration (auto-reassign/retry planners) - -Role-based human permission granularity is V1 — see the `humans-and-permissions` -plan, the `principal_permission_grants` table, and the `PERMISSION_KEYS` set -in `packages/shared/src/constants.ts`. - -## 6. Architecture - -### Experimental persistent cases and handoffs - -Standalone cases (`enableCases`) are independent of the experimental pipeline -case model. A passive case has no execution requirement. `work_issue_id` binds at -most one primary ordinary task per case and one primary case per task. Handoffs -reuse that task, update its assignee through normal task policy, and store a free -text phase separately from case/task status. They do not satisfy review gates. - -Native `create_case` requires `kickoff` and exactly one of `workflowId` or -`workflow_document_text`, delegating to ordinary case handoff acceptance. -New-case API handoffs accept the same choice. The selected prompt is saved verbatim as ordinary task document key `workflow`, title -`Workflow`, format `markdown`. Missing or blank text is rejected before mutation. -Top-level `initialDocuments` belongs to first-task kickoff: normalized, unique -task document keys, at most 20 additional documents and 200,000 total body -characters including the workflow. Its key is reserved from additional seeds. -The acceptance transaction writes -ordinary task documents, attribution and activity before durable wake delivery. -Retries share the kickoff receipt and cannot repeat document revisions. Existing -primary tasks, binding existing tasks and terminal handoffs reject initial -documents. There is no passive native creation branch or document seed storage. Native case-document -tools are retired; case detail and execution context roll up authorized task -document references, omitting bodies and system keys. Historical case document -storage and REST reads remain compatible. - -Starting the first new task of an existing passive case also requires a workflow -ID or inline workflow text. New-case binding may create `workflow` on the bound task with ordinary task -mutation authority and create-only revision semantics; an existing key conflicts -atomically. Later handoffs of an existing primary task omit workflow text and -revise it through ordinary task document tools. Existing stored cases are not -backfilled. Receiving agents are instructed to read the task's workflow document; -its authorized metadata precedes the document reference budget, while its body -is read on demand. Recommend both a Mermaid `stateDiagram-v2` map and prose -defining each transition's trigger, evidence, destination agent, human wait/resume -rules, and correction/escalation limits. State IDs match phases and named edge -outcomes are defined in prose. No graph parser, guard interpreter or new workflow -execution service is added. - -Reusable `workflows` are company-scoped definitions with a name, Markdown prompt, -and ordinary attribution/timestamps. Cases hold an optional `workflowId`; their -case type, facts, status and free-text phase remain independent. There are no -workflow versions, workflow runs, state graphs, or role bindings. The task's -`workflow` document is the effective procedure and owns its revision history. -Editing a shared definition affects future adoptions only. - -Agent handoffs omit `workflowId` to preserve the relationship and document, -supply an ID to adopt the latest prompt (including the current ID to refresh), -or supply null to detach while retaining instructions. Acceptance snapshots the -selected prompt, previous/next workflow IDs, names and phases, and the document's -base revision. Transfer applies the relationship, phase and effective document -revision in one transaction after outgoing execution settles. Intervening -changes to the workflow document hold adoption with the existing revision -conflict; cancel and submit new intent after reading current state. Cancelled, -unapplied intent changes neither instructions nor workflow routing. Applied -retries never create another document revision. Generic case updates cannot -change workflow routing. Historical handoffs remain readable without inferred -workflow assignments, and repeated visits are distinct handoffs. Captured names -survive renames. Terminal completion semantics are unchanged. - -Company list/create and individual read/update workflow endpoints, plus native -`list_workflows`, `read_workflow`, `create_workflow` and `update_workflow`, share -`experimental.enableCases`. Lists and mutation receipts return metadata; prompt -reads are on demand, using bounded native API JSON text pages. The existing -company authorization, source-trust and run mutation fences apply. Definitions -are company-shared; no case document is automatically published into them. -Existing case pages and Audit show current and captured workflow names/phases, -distinguishing pending intent from applied transitions. Authoring is API/agent -first; there is no workflow editor or deletion lifecycle. - -`case_handoffs` is a durable, principal-idempotent intent/delivery ledger. -Acceptance atomically stores the comment, links, observed revisions, audit, and -delivery identity. A running source yields; it retains execution ownership until -provider termination and workspace/session settlement are proven. Its late -mutations cannot complete transferred work. Delivery uses the ordinary wake queue -with a stable receipt, preserving Stop, private access, responsible-user, pause, -and budget rules. A queued receipt is not proof that the recipient ran. -Whole-company deletion removes cases and their history before tasks and agents. -Permanent deletion of an individual agent with retained handoff history returns -`409 agent_case_history_retained`; keep that agent terminated to preserve attribution. - -An existing case with a primary task may instead receive `to: "done"`. -This terminal handoff records `awaiting_completion`, keeps assignment and -execution unchanged, and returns `callerMustYield: false`. Normal task -completion remains authoritative: accepted transition to `done` atomically -closes the case, sets its phase to `done` and records the handoff as `completed`. -A task already done settles immediately. Reviews, questions and other legitimate -waits do not close the case. Stop, task cancellation and reassignment cancel -the intent. Recording a terminal intent cannot manufacture completion evidence -or bypass review. Ordinary task completion without this intent leaves case -status unchanged. Settling an admitted intent remains available while cases -are disabled, like outgoing-execution cleanup. - -`case_access_sources` retains existing task/project boundaries. All sources must -remain readable; unlinking or deletion cannot widen access. The same policy covers -case contents, documents, revisions, annotations, assets, history, audit, and live -publication. Agents cannot supply creator or responsible-user overrides. - -The REST operation is `POST /api/companies/:companyId/case-handoffs`; native agents -use `handoff_case`. History, retry, and cancel stay on the case. Workflow routing -lives in prompts/skills, without a server-configured phase graph. See -[persistent case handoffs](plans/2026-10-19-persistent-case-handoffs.md). - -Native standard-mode agents also have `create_case`, `read_case` and -`update_case`. Reads and updates default to the task's primary case and preserve -the standalone-case authorization, provenance and audit rules of REST. Case -updates expose facts only; routing, status and permissions remain outside those -updates. Update receipts deduplicate identical keys within one admitted run; -creation and handoff receipts remain principal-scoped. Ordinary `read_document` -and `write_document` manage the active task's revisioned documents. - -`read_case` keeps small responses structured. Large responses return bounded -`readPage.text` JSON chunks with case/task routing versions on every page. Use -`section: "fields"` to retrieve the entire fields object, including large keys, -strings, and arrays. Continue with the same `section` and `snapshot`, passing -`offsetBytes: readPage.nextOffsetBytes` until it is null; concatenate the text -before parsing JSON. A 409 means facts, accessible references, or routing changed: -restart at offset zero without the old snapshot. Each page reauthorizes current case access through -`GET /api/cases/:id?agentRead=true`; no generic API tool or asset grant is needed. -`limitBytes` is 4–8192 UTF-8 bytes. Ordinary REST reads retain their existing full -response, and ordinary `update_case` mutation receipts remain unchanged. -For `section: "document"`, require `documentKey` and optionally -`documentIssueId`. Omit the task ID for historical case-owned documents; a -supplied ID must identify the case's primary or currently linked task. Return -the selected document as bounded JSON pages. Recheck source access, linkage, -and document trust on every page, and reject system or quarantined documents. -Document identity, revision and body participate in the snapshot. These reads -work without generic API tools and confer no additional mutation authority. - -Case context is authorized on each admission. Fresh sessions receive a bounded -snapshot; verified native continuations send current routing metadata and new -handoff instructions, plus case facts when their content or document revisions -change. An incompatible or unproven prior projection falls back to full context. -This limits repeated input without treating prepared database snapshots as proof -that a provider received them. - -The existing Cases navigation opens read-only list and detail pages. List filters -are URL-backed and applied before authorized pagination. A distinct-types read -uses the same case visibility policy. Rows include batched current work context; -detail preserves document previews/revisions, attachments, references, activity -and handoff history. No case mutation or handoff recovery controls appear there. - -## 6.1 Runtime Components - -- `server/`: REST API, auth, orchestration services -- `ui/`: Board operator interface -- `packages/db/`: Drizzle schema, migrations, DB clients (Postgres) -- `packages/shared/`: Shared API types, validators, constants - -## 6.2 Data Stores - -- Primary: PostgreSQL -- Local default: embedded PostgreSQL at `~/.paperclip/instances/default/db` -- Optional local prod-like: Docker Postgres -- Optional hosted: Supabase/Postgres-compatible -- File/object storage: - - local default: `~/.paperclip/instances/default/data/storage` (`local_disk`) - - cloud: S3-compatible object storage (`s3`) - -## 6.3 Background Processing - -A lightweight scheduler/worker in the server process handles: - -- heartbeat trigger checks -- stuck run detection -- budget threshold checks - -Separate queue infrastructure is not required for V1. - -## 7. Canonical Data Model (V1) - -All core tables include `id`, `created_at`, `updated_at` unless noted. - -## 7.0 Auth Tables - -Human auth tables (`users`, `sessions`, and provider-specific auth artifacts) are managed by the selected auth library. This spec treats them as required dependencies and references `users.id` where user attribution is needed. - -## 7.1 `companies` - -- `id` uuid pk -- `name` text not null -- `description` text null -- `status` enum: `active | paused | archived` -- `pause_reason` text null -- `paused_at` timestamptz null -- `issue_prefix` text not null -- `issue_counter` int not null -- `budget_monthly_cents` int not null default 0 -- `spent_monthly_cents` int not null default 0 -- `require_board_approval_for_new_agents` boolean not null default false -- feedback sharing consent fields - -Invariant: every business record belongs to exactly one company. - -## 7.2 `agents` - -- `id` uuid pk -- `company_id` uuid fk `companies.id` not null -- `name` text not null -- `role` text not null -- `title` text null -- `icon` text null -- `status` enum: `active | paused | idle | running | error | pending_approval | terminated` -- `reports_to` uuid fk `agents.id` null -- `capabilities` text null -- `adapter_type` text; built-ins include `process`, `http`, `claude_local`, `codex_local`, `gemini_local`, `opencode_local`, `pi_local`, `cursor`, `hermes_local`, `hermes_gateway`, and `openclaw_gateway` -- `adapter_config` jsonb not null -- `runtime_config` jsonb not null default `{}`; contains Paperclip runtime policy such as heartbeat scheduling and debug settings -- `default_environment_id` uuid fk `environments.id` null -- `context_mode` enum: `thin | fat` default `thin` -- `budget_monthly_cents` int not null default 0 -- `spent_monthly_cents` int not null default 0 -- pause fields: `pause_reason`, `paused_at` -- `permissions` jsonb not null default `{}` -- `last_heartbeat_at` timestamptz null -- `metadata` jsonb null -- `created_by_user_id` text null; the human user who created or requested the agent. Null for agent-requested and system-created agents. The agent overview shows it only when the company has more than one human member; the local Board principal is never counted or shown. - -Invariants: - -- agent and manager must be in same company -- no cycles in reporting tree -- `terminated` agents cannot be resumed - -Agent configuration also has a versioned YAML representation in the Advanced -tab, including instruction text, skills, tools, channels, permissions, secrets, -API key operations, and revision restore. Resource saves use their owning APIs -and expose partial failures; they are not a cross-resource transaction. -[Agent configuration YAML](agent-configuration-yaml.md) defines its schema, -replacement semantics, secret preservation, and revision preconditions. It uses -the existing configuration and resource APIs, retaining their authorization -and approval gates. Agent lifecycle commands remain separate actions. - -`agents:read` permits redacted configuration, configured skills, instruction -content and history reads without write authority. `agents:configure` remains -read/write; `agents:suggest-changes` retains reads and consented changes. Own -configuration/history reads require no peer-read grant. Target scopes, company -boundaries and responsible-user authority still apply. Standard agents receive -read access by default, with a one-time read-only rollout to eligible existing -agents. See [agent permission defaults](agent-permission-defaults.md). - -## 7.3 `agent_api_keys` - -- `id` uuid pk -- `agent_id` uuid fk `agents.id` not null -- `company_id` uuid fk `companies.id` not null -- `name` text not null -- `key_hash` text not null -- `last_used_at` timestamptz null -- `revoked_at` timestamptz null - -Invariant: plaintext key shown once at creation; only hash stored. - -### Agent cryptographic identity - -Each agent also has one Ed25519 identity in `agent_identity_keys`, separate from -API bearer keys and company secrets. New-agent creation provisions it atomically; -existing agents provision lazily before their first managed run. The schema-only -migration and public reads never provision existing agents. Private PKCS#8 PEM -material uses `local_encrypted`; public SPKI PEM and its SHA-256 key ID are readable -through `GET /api/agents/:id/identity` and the agent Identity section. Managed -processes receive the pair through runtime-only environment fields. See -[Agent cryptographic identity](AGENT-IDENTITY.md) for storage, runtime, and copy -semantics. - -## 7.4 `goals` - -- `id` uuid pk -- `company_id` uuid fk not null -- `title` text not null -- `description` text null -- `level` enum: `company | team | agent | task` -- `parent_id` uuid fk `goals.id` null -- `owner_agent_id` uuid fk `agents.id` null -- `status` enum: `planned | active | achieved | cancelled` - -Invariant: at least one root `company` level goal per company. - -## 7.5 `projects` - -- `id` uuid pk -- `company_id` uuid fk not null -- `goal_id` uuid fk `goals.id` null -- `name` text not null -- `description` text null -- `visibility` enum-like text: `open | private`, default `open` -- `personal_owner_user_id` text null; unique with `company_id` when present -- `status` enum: `backlog | planned | in_progress | completed | cancelled` -- `lead_agent_id` uuid fk `agents.id` null -- `target_date` date null -- `env` jsonb null (same secret-aware env binding format used by agent config) - -Invariant: - -- project env is merged into run environment for issues in that project and overrides conflicting agent env keys before Paperclip runtime-owned keys are injected - -Routine execution issues add a routine-scoped env overlay after project env and before Paperclip runtime-owned keys. Routine env uses the same secret-aware binding format, is stored on `routines.env`, is snapshotted in routine revisions, and resolves secret refs against the routine binding target so routine-owned secrets do not require direct bindings on the executing agent. - -Project source repositories use the existing `project_workspaces` collection. -Each selected GitHub repository has a canonical `repo_url` and stable provider ID -in `metadata.githubRepositoryId`; the first workspace remains the execution default. -The board can select multiple repositories from its usable personal and shared -GitHub grants. Selection does not grant runtime credential access. Legacy workspace -URLs remain valid. Project creation and repository replacement are transactional. -See `doc/project-repositories.md` for the API and UI contract. - -## 7.6 `issues` (core task entity) - -Task creation accepts an omitted or blank title when the description contains a prompt. -The server stores the first 120 characters of its whitespace-normalized prompt as -a provisional title and sets `title_needs_generation`. The assigned agent receives -an early instruction to use `set_task_title` (or `PUT /api/issues/:id/title`) to -replace it with a concise title. `onlyIfProvisional: true` atomically preserves a -user or agent title already chosen, including concurrent edits. Explicit title -edits clear the marker. Naming is available in standard, ask, and planning modes; -it does not change status, ownership, or the full description. Title writes are -company-scoped and audited; agent callers must own the active run. Existing tasks -retain their titles and default to no generation request. - -- `id` uuid pk -- `company_id` uuid fk not null -- `project_id` uuid fk `projects.id` null -- `project_workspace_id` uuid fk `project_workspaces.id` null -- `goal_id` uuid fk `goals.id` null -- `parent_id` uuid fk `issues.id` null -- `visibility` enum-like text: `open | private`, default `open` -- `privacy_root_issue_id` uuid fk `issues.id` null; private-boundary metadata -- `privacy_parent_issue_id` uuid fk `issues.id` null; downward access inheritance, including standalone run-created handoffs -- `origin_issue_id` uuid fk `issues.id` null, on delete set null; audit-only creation provenance (see below), never hierarchy or authorization -- `title` text not null -- `description` text null -- `status` enum: `backlog | todo | in_progress | in_review | done | blocked | cancelled` -- Task reads additionally expose read-only `displayStatus`: the lifecycle status, or `monitoring` for an agent-owned scheduled ordinary monitor wait with no active execution, locks, review participant or active recovery action. Board collection/search `displayStatus` filters and counts use the same projection before pagination. This field is not persisted and cannot authorize a status transition; existing `status` filters retain their lifecycle semantics. -- `priority` enum: `critical | high | medium | low` -- `review_policy` nullable enum: `anyone | not_creator | human_only`; null is equivalent to `anyone` -- `assignee_agent_id` uuid fk `agents.id` null -- `assignee_user_id` text null -- checkout/execution locks: `checkout_run_id`, `execution_run_id`, `execution_agent_name_key`, `execution_locked_at` -- `created_by_agent_id` uuid fk `agents.id` null -- `created_by_user_id` uuid fk `users.id` null -- identifier fields: `issue_number`, `identifier` -- origin fields: `origin_kind`, `origin_id`, `origin_run_id`, `origin_fingerprint` -- Creation stores the actor run in `origin_run_id` unless an explicit origin run is supplied. `GET /api/companies/:companyId/issues?createdFromIssueId=` selects tasks whose `origin_issue_id` is that source task. Only for rows without a recorded `origin_issue_id`, it falls back to tasks created by runs bound to the source task, using native run issue identity or persisted legacy task context; a recorded origin suppresses that fallback, so the list agrees with `createdFrom`. Historical rows without an origin run may use their recorded creation activity; comments and shared creators do not establish provenance. Source, run, activity and result are company-scoped. -- A child task never shares its parent's (or any ancestor's) execution workspace. It inherits company, project, goal, privacy and workspace strategy, and its first run realizes its own workspace from the project base. `inheritExecutionWorkspaceFromIssueId` copies only the source issue's project and strategy; an explicit `executionWorkspaceId` that an ancestor created or is bound to is dropped. A child that an older release bound to its parent's workspace is moved to its own on its next admission once no earlier run of it is still running or settling (see `doc/execution-semantics.md`). -- Delegated follow-ups keep their structural parent. When an agent run bound to an ordinary execution task creates an issue and omits `parentId`, the server defaults `parentId` to that run's task and records `parentDefaultedFromRunIssue: true` on the `issue.created` activity. An explicit `parentId: null` keeps the task standalone. The default never applies to conversation runs (chat handoffs stay top-level), to watchdog follow-ups, to board/user creates, to a run task the agent may not mutate, when the default parent would create a delegation cycle with the requested assignee, or when the agent may not create a child under the default parent (for example a protected assignment policy without a grant); those requests stay standalone instead of failing. An explicitly requested parent is still subject to the ordinary assignment check. Structural parentage, creation provenance and blockers stay distinct: the default adds no blocker. -- Agent-created task trees are at most two levels (`MAX_AGENT_TASK_HIERARCHY_DEPTH`). When an agent, or automation acting for one (runner and project tools, chat suggestions, native delegation, accepted-plan decomposition, decision effects, plugins, external agents), would create or move a task to a third level, the server does not reject it: the task is placed under the requested parent's ancestor at the allowed depth, a sibling of the requester, and the create/update response and activity carry `parentAdjusted`. An agent move of a task that has its own children skips only the parent change (`reason: "has_children"`). The requester keeps blocking (`blockParentUntilDone` blocks the requester), workspace strategy inheritance and privacy inheritance; provenance follows the `origin_issue_id` rule below. Placement never moves a task above a low-trust boundary root that contains the requester. The ancestor is written only when the caller authorized it and confirms it with `expectedParentId` (the issue routes, the runner `create_task` tool, and nested chat suggestions do); other callers get a standalone task instead (an unconfirmed move skips only the parent change), so no edge lands under an unchecked ancestor. Board users, watchdog and recovery machinery, channel-boundary placement and human-configured routines may nest to any depth; existing deeper trees stay valid and fully readable. -- `origin_issue_id` and `origin_run_id` are server-set only: the task the creating run was bound to (a chat included), otherwise the chat that handed the task off, otherwise the requested parent (before placement); and that run. Privacy still follows the requested parent. Because `origin_issue_id` is returned with the task, it is recorded only when it reveals nothing new: the task's own parent or privacy parent (already exposed on the task), or a fully public origin (open, outside private projects and subtrees, not hidden); otherwise it stays null and `createdFrom` resolves the source through the run with its access checks. They never change after create. -- `GET /api/issues/:id` returns `createdFrom` (`{ issue: { id, identifier, title, status }, run: { id, agentId } | null, agent: { id, name } | null } | null`): the task that created this issue, resolved from `origin_issue_id`, otherwise from the task the origin run was executing (`origin_run_id`, or for historical rows the run on the recorded creation activity). It is present whether or not `parentId` is set and requires that the viewer may read the source task. A recorded `origin_issue_id` is returned with `run` and `agent` null when no run created the task or the viewer may not see that run, so a null `run` is not missing provenance; a source resolved only through a run additionally requires that run to be visible. An unreadable, hidden-from-viewer or cross-company source yields `null`, indistinguishable from missing provenance. -- Relation lists can use `sortField=id&sortDir=asc&afterId=` for stable pagination. The cursor excludes earlier IDs and cannot be combined with an offset or activity-based order. -- The streamlined task page's Tasks tab keeps two independent memberships: the existing subtask tree, and created tasks grouped by their current project (or No project). A created subtask appears in both. Only Subtasks has completion progress; groups collapse independently and unfinished tasks sort above finished tasks. -- `request_depth` int not null default 0 -- `work_mode` text not null default `standard`; supported values: - - `standard`: normal autonomous execution. Agents may investigate, edit files, create artifacts, and complete the task. - - `ask`: answer-only execution. Agents may use tools for investigation or temporary scratch work, but the deliverable is an issue-thread answer; they must not write implementation code or produce an implementation plan. - - `planning`: plan-only execution. Agents create or revise the plan without implementation work. Accepting a fresh confirmation for the issue's current `plan` revision atomically changes this mode to `standard`, so the continuation may implement the approved plan on the source issue. -- Harness-proposed plans: a native run's own proposed-plan surface (Codex plan collaboration mode `plan` item or `` block, Claude Code ExitPlanMode) emits the canonical `plan.proposed` event. A completed proposal is saved as the issue's `plan` document and gated by a `request_confirmation` bound to that revision, in `planning` and `standard` work mode alike. A `planning` run starts the harness in its native plan mode. Synchronization never changes the issue status mid-run; the run finalizer turns the pending confirmation into the review wait. An agent-initiated proposal on a `standard` issue leaves the work mode unchanged. -- Work mode is explicit persisted state. Titles, descriptions, and requested deliverables never select or change it. A `standard` task may deliver a requested plan, including a canonical `plan` document, and complete without entering `planning` mode. Existing explicit approval requirements still apply. -- `billing_code` text null -- `assignee_adapter_overrides` jsonb null -- `execution_policy` jsonb null -- `execution_state` jsonb null -- execution workspace fields: `execution_workspace_id`, `execution_workspace_preference`, `execution_workspace_settings` -- `started_at` timestamptz null -- `completed_at` timestamptz null -- `cancelled_at` timestamptz null -- `hidden_at` timestamptz null - -Invariants: - -- single assignee only -- task must trace to company goal chain via `goal_id`, `parent_id`, or project-goal linkage -- `in_progress` requires assignee -- agent task presentation reserves **In Progress** for a task-linked queued or running run; inactive tasks project recovery as **Blocked**, pending responses/review as **In Review**, scheduled checks as **Monitoring**, or remaining work as **Todo** without changing lifecycle/admission state -- an `in_review -> done | cancelled` verdict is authorized against the current review policy while the issue row is locked; a policy change in the same request or a concurrent request cannot relax that verdict gate -- accepting or rejecting the review-confirmation interaction locks the issue row before resolving the interaction and reauthorizes against the current review policy in that transaction -- accepting a fresh `request_confirmation` for the current issue's `plan` revision changes `work_mode = planning` to `work_mode = standard` in the same transaction as the accepted interaction; the existing agent-return transition also moves an eligible `in_review` issue to `todo` without changing its agent owner -- while a restrictive review policy is stored, changing it requires an actor who is allowed by that row-locked policy -- the transition into `in_review` and its requester activity record commit atomically, including transitions without an explicit review-interaction binding -- terminal states: `done | cancelled` - -## 7.7 `issue_comments` - -- `id` uuid pk -- `company_id` uuid fk not null -- `issue_id` uuid fk `issues.id` not null -- `author_agent_id` uuid fk `agents.id` null -- `author_user_id` uuid fk `users.id` null -- `body` text not null - -## 7.7.1 `issue_access_grants` - -- `issue_id` uuid fk `issues.id` not null -- `subject_type` enum-like text: `user | agent` -- `subject_id` text not null -- `source` enum-like text: `explicit | assignment | project` -- `granted_by_user_id` text null -- `granted_by_agent_id` uuid fk `agents.id` null -- `created_at` timestamptz not null -- `revoked_at` timestamptz null - -An issue is readable when it is open outside a private project, the actor is an implicit principal (`responsible_user_id`, `created_by_user_id`, `assignee_user_id`, or current `assignee_agent_id`), the actor has an unrevoked grant on the issue or a private ancestor, or the actor is an access member of its private project. This is one canonical predicate shared by detail, list, count, search, and run-derived reads. Instance administrators retain platform-wide access; company owner/admin roles do not imply private-issue read access. Sharing a child covers that child and descendants; ancestors and siblings require separate access. Agent reads also require the responsible user to qualify. A private parent protects existing and newly created descendants; making a parent public does not publish private descendants. - -## 7.7.2 `project_access_members` - -Authorization membership for private projects, distinct from the personal-sidebar `project_memberships` table: - -- `id` uuid pk -- `company_id` uuid fk `companies.id` not null -- `project_id` uuid fk `projects.id` not null -- `subject_type` enum-like text: `user | agent` -- `subject_id` text not null -- `created_at` timestamptz not null -- unique `(project_id, subject_type, subject_id)` - -Private projects are absent from list and search results for non-members, and direct reads return `404`. An issue-level grant can still expose its issue without exposing the containing project. A user's `My private tasks` project is created lazily and concurrency-safely on their first unprojected private task; the owning user is its initial and non-removable project member. An agent-created private root also grants that creator issue-level access while retaining the responsible user as the human principal. - -## 7.8 `heartbeat_runs` - -- `id` uuid pk -- `company_id` uuid fk not null -- `agent_id` uuid fk not null -- `issue_id` uuid fk `issues.id` null; first-class binding for issue-scoped run confidentiality -- `invocation_source` enum: `scheduler | manual | callback` -- `status` enum: `queued | running | succeeded | failed | cancelled | timed_out` -- `started_at` timestamptz null -- `finished_at` timestamptz null -- `error` text null -- `external_run_id` text null -- `context_snapshot` jsonb null - -## 7.9 `cost_events` - -- `id` uuid pk -- `company_id` uuid fk not null -- `agent_id` uuid fk `agents.id` not null -- `issue_id` uuid fk `issues.id` null -- `project_id` uuid fk `projects.id` null -- `goal_id` uuid fk `goals.id` null -- `billing_code` text null -- `provider` text not null -- `model` text not null -- `cost_status` text not null default `reported`; `unpriced` when usage exists but no price was reported -- `input_tokens` int not null default 0 -- `output_tokens` int not null default 0 -- `cost_cents` int not null -- `occurred_at` timestamptz not null - -Invariant: each event must attach to agent and company; rollups are aggregation, never manually edited. - -## 7.10 `approvals` - -- `id` uuid pk -- `company_id` uuid fk not null -- `type` enum: `hire_agent | approve_ceo_strategy | budget_override_required | request_board_approval` -- `requested_by_agent_id` uuid fk `agents.id` null -- `requested_by_user_id` uuid fk `users.id` null -- `status` enum: `pending | revision_requested | approved | rejected | cancelled` -- `payload` jsonb not null -- `decision_note` text null -- `decided_by_user_id` uuid fk `users.id` null -- `decided_at` timestamptz null - -## 7.11 `activity_log` - -- `id` uuid pk -- `company_id` uuid fk not null -- `actor_type` enum: `agent | user | system` -- `actor_id` uuid/text not null -- `action` text not null -- `entity_type` text not null -- `entity_id` uuid/text not null -- `details` jsonb null -- `created_at` timestamptz not null default now() - -## 7.12 `project_memberships` + `agent_memberships` - -Per-user project/agent membership is personal visibility state for board users. It only controls whether a resource appears in the current user's sidebar; it must not grant or revoke access to all-pages, detail pages, selectors, assignment flows, search, or existing permissions. - -`project_memberships`: - -- `id` uuid pk -- `company_id` uuid fk `companies.id` not null -- `project_id` uuid fk `projects.id` not null -- `user_id` text not null -- `state` enum-like text: `joined | left` -- `created_at` timestamptz not null default now() -- `updated_at` timestamptz not null default now() -- unique `(company_id, user_id, project_id)` - -`agent_memberships` mirrors the same shape with `agent_id` instead of `project_id` and unique `(company_id, user_id, agent_id)`. - -Invariants: - -- Missing membership rows mean `joined` for backward compatibility. -- Mutations are board-user-only `/me` operations; agent API keys are rejected. -- Viewer-role board users may update only their own membership rows through the narrow self-service helper. -- Target project/agent ownership is checked against the path company before mutation. -- Successful state changes write `resource_membership.joined` or `resource_membership.left` activity entries. - -## 7.13 `company_secrets` + `company_secret_versions` - -- Secret values are not stored inline in `agents.adapter_config.env`. -- Agent env entries should use secret refs for sensitive values. -- `company_secrets` tracks identity/provider metadata per company. -- `company_secret_versions` stores encrypted/reference material per version. -- Default provider in local deployments: `local_encrypted`. - -Operational policy: - -- Config read APIs redact sensitive plain values. -- Activity and approval payloads must not persist raw sensitive values. -- Config revisions may include redacted placeholders; such revisions are non-restorable for redacted fields. - -## 7.14 Required Indexes - -- `agents(company_id, status)` -- `agents(company_id, reports_to)` -- `issues(company_id, status)` -- `issues(company_id, assignee_agent_id, status)` -- `issues(company_id, parent_id)` -- `issues(company_id, project_id)` -- `cost_events(company_id, occurred_at)` -- `cost_events(company_id, agent_id, occurred_at)` -- `heartbeat_runs(company_id, agent_id, started_at desc)` -- `heartbeat_runs(company_id, issue_id, created_at)` -- `approvals(company_id, status, type)` -- `activity_log(company_id, created_at desc)` -- `assets(company_id, created_at desc)` -- `assets(company_id, object_key)` unique -- `issue_attachments(company_id, issue_id)` -- `company_secrets(company_id, name)` unique -- `company_secret_versions(secret_id, version)` unique -- `project_memberships(company_id, user_id)` -- `project_memberships(company_id, user_id, project_id)` unique -- `agent_memberships(company_id, user_id)` -- `agent_memberships(company_id, user_id, agent_id)` unique - -## 7.15 `assets` + `issue_attachments` - -- `assets` stores provider-backed object metadata (not inline bytes): - - `id` uuid pk - - `company_id` uuid fk not null - - `provider` enum/text (`local_disk | s3`) - - `object_key` text not null - - `content_type` text not null - - `byte_size` bigint not null (safe integer byte count in the API) - - `sha256` text not null - - `original_filename` text null - - `created_by_agent_id` uuid fk null - - `created_by_user_id` uuid/text fk null -- `issue_attachments` links assets to issues/comments: - - `id` uuid pk - - `company_id` uuid fk not null - - `issue_id` uuid fk not null - - `asset_id` uuid fk not null - - `issue_comment_id` uuid fk null -- V1 attachment serving contract: - - Default upload allowlist includes common images, PDF, plain text/markdown/JSON/CSV/HTML, ZIP, and video artifacts (`video/mp4`, `video/webm`, `video/quicktime`). - - Attachment reads are company-scoped and expose stable path metadata: `contentPath`/`openPath` for inline-safe viewing and `downloadPath` for forced download. - - Inline-safe responses use `Content-Disposition: inline`; unsafe types and explicit download requests use `attachment`. - - Script-capable content such as HTML is always served as an attachment with `X-Content-Type-Options: nosniff` and a sandboxed, deny-by-default CSP; it is never rendered inline on the Paperclip origin. - - The board can fetch bounded HTML source for a rich preview in an opaque-origin iframe (`sandbox="allow-scripts"`, without `allow-same-origin`). A trusted CSP precedes artifact markup and blocks remote subresources, API connections, forms, and embeds. Rendered/raw icons sit beside Download. Workspace HTML previews return UTF-8 inside JSON under the existing text limits. See `doc/html-artifact-previews.md`. - - Video attachments are inline-safe and support single `Range: bytes=start-end` requests with `206`, `Content-Range`, and `Accept-Ranges: bytes` for browser playback/seeking. -- Attachment-backed artifact work products use `type: "artifact"`, `provider: "paperclip"`, and metadata with `attachmentId`, `contentType`, `byteSize`, `contentPath`, `openPath`, `downloadPath`, and optional `originalFilename`. -- Workspace-only file references use work product `metadata.resourceRef` with `kind: "workspace_file"`, `issueId`, `workspaceKind` (`execution_workspace` or `project_workspace`), `workspaceId`, `relativePath`, optional `line`/`column`, and `displayPath`. These references point at files in a workspace; they do not replace attachment-backed artifacts for deliverables that must be inspectable without workspace access. - -## 7.15 `documents` + `document_revisions` + `issue_documents` - -- `documents` stores editable text-first documents: - - `id` uuid pk - - `company_id` uuid fk not null - - `title` text null - - `format` text not null (`markdown`) - - `latest_body` text not null - - `latest_revision_id` uuid null - - `latest_revision_number` int not null - - `created_by_agent_id` uuid fk null - - `created_by_user_id` uuid/text fk null - - `updated_by_agent_id` uuid fk null - - `updated_by_user_id` uuid/text fk null - - `locked_at` timestamptz null - - `locked_by_agent_id` uuid fk null - - `locked_by_user_id` uuid/text fk null - - Locked documents are immutable until unlocked. Board operators can lock/unlock; agent writes to a locked key create a new issue document with a derived key instead of overwriting the locked document. -- `document_revisions` stores append-only history: - - `id` uuid pk - - `company_id` uuid fk not null - - `document_id` uuid fk not null - - `revision_number` int not null - - `body` text not null - - `change_summary` text null -- `issue_documents` links documents to issues with a stable workflow key: - - `id` uuid pk - - `company_id` uuid fk not null - - `issue_id` uuid fk not null - - `document_id` uuid fk not null - - `key` text not null (`plan`, `design`, `notes`, etc.) - -## 7.16 Current Implementation Addenda - -The current implementation includes additional V1-control-plane tables beyond the original February snapshot: - -- Issue structure and review: `issue_relations` for blockers, `labels`/`issue_labels`, `issue_thread_interactions`, `issue_approvals`, `issue_execution_decisions`, `issue_work_products`, `issue_inbox_archives`, `issue_read_states`, and issue reference mention indexes. -- Execution and workspace control: `execution_workspaces`, `project_workspaces`, `workspace_runtime_services`, `workspace_operations`, `environments`, `environment_leases`, `agent_task_sessions`, `agent_runtime_state`, `agent_wakeup_requests`, heartbeat events, and watchdog decision tables. -- Plugins and routines: `plugins`, plugin config/state/entities/jobs/logs/webhooks, plugin database namespaces/migrations, plugin company settings, `routines`, `routine_revisions`, `routine_triggers`, and `routine_runs`. -- Access and operations: company memberships, instance roles, principal permission grants, invites, join requests, board API keys, CLI auth challenges, budget policies/incidents, feedback exports/votes, company skills, sidebar preferences, and company logos. - -Decision-desk triage uses company-scoped sidecars rather than adding queue fields to every attention source: - -- `decision_queues` stores durable named queues, optional retention overrides, server-derived creator/run provenance, and data-backed seed rules. -- `decision_queue_items` keys membership by `(queue_id, source_kind, source_id)` and repeats `company_id` for company-consistent joins. -- `decision_triage` keys current decide-by/snooze state by `(company_id, source_kind, source_id)` and preserves the latest setter attribution. -- `decision_triage_events` is the immutable mutation history for queue membership and triage overrides, including actor, run, API-key, and responsible-user provenance. -- `decision_retention` stores the attention source's last observed activity timestamp, monotonic version, Keep flag, and reversible archive provenance. Queue `retention_days` overrides use the shortest assigned queue threshold; otherwise the shelf threshold is 30 days. -- `decision_archive_notification_outbox` records one retry-safe origin-agent notification per source/archive version. The 90-day internal sweeper archives only unkept rows and coalesces delivery per origin agent. -- Queue membership never grants source visibility. Item writes re-authorize the referenced source, and queue reads re-authorize every member before returning rows or counts. - -## 7.17 Personal primary agent (2026-10-07) - -`user_company_preferences` has a unique company/user key, nullable `primary_agent_id`, -and sticky `primary_agent_initialized` flag. GET/PUT -`/api/companies/:companyId/primary-agent/me` derives the human identity from auth, -checks company access and agent visibility, rejects agent actors, and audits changes. -PUT accepts an approved, non-terminated agent; choosing a previously left agent -rejoins it under the existing membership rules. There is no explicit removal UI or -nullable PUT. Leaving, termination, and deletion clear the reference without -allowing a later creation to initialize it again. Pausing and errors retain it. - -Initialization runs in the human-attributed creation transaction, including -onboarding, with uniqueness arbitrating concurrent creation. System provisioning -and agent-authored hires cannot initialize a human preference. Migration backfill -uses the earliest attributable human creation, preserving empty initialized state -when the original is gone, terminated, or left; unknown ownership stays unset. - -The profile is the only setting surface. Replacing a primary requires the reviewed -avatar-to-avatar confirmation. A first choice has no modal. Only the profile and -roster show the accessible crown; the Agents sidebar pins the primary first without -duplication. Tasks preserve explicit/draft assignments, then choose a recent -eligible assignee, the eligible primary, or the existing fallback. Chat preserves -a valid recent conversation, then opens the primary, then retains its chooser. -Navigation alone does not create an execution. Failed preference mutations restore -the previous state and offer retry. Preferences and caches are company/user scoped. - -## 8. State Machines - -## 8.1 Agent Status - -Allowed transitions: - -- `idle -> running` -- `running -> idle` -- `running -> error` -- `error -> idle` -- `idle -> paused` -- `running -> paused` (requires cancel flow) -- `paused -> idle` -- `* -> terminated` (board only, irreversible) - -## 8.2 Issue Status - -Allowed transitions: - -- `backlog -> todo | cancelled` -- `todo -> in_progress | blocked | cancelled` -- `in_progress -> in_review | blocked | done | cancelled` -- `in_review -> in_progress | done | cancelled` -- `blocked -> todo | in_progress | cancelled` -- terminal: `done`, `cancelled` - -Side effects: - -- entering `in_progress` sets `started_at` if null -- entering `done` sets `completed_at` -- entering `cancelled` sets `cancelled_at` - -V1 non-terminal liveness rule: - -- agent-owned `todo`, `in_progress`, `in_review`, and `blocked` issues must have a live execution path, an explicit waiting path, or an explicit recovery path -- `in_review` is healthy only when a typed execution participant, pending issue-thread interaction or approval, user owner, active run, queued wake, or explicit recovery action owns the next action -- A published advisory GitHub review can also remain `in_review` through an authorized external response wait or a saved monitor. Rating, CI state, and merge state alone do not create human attention. The current committed wait is an action path while its assigned endpoint and conversation remain active; it creates no Mine participation. -- a blocked chain is covered only when each unresolved leaf issue is live or explicitly waiting -- external waits are durable only when persisted as a bounded monitor/scheduled wake, a first-class blocker with a named owner and action, or healthy delegated child work connected by a blocker edge when the source must wait; parent/child structure alone is not a wait path -- automatic monitor checks require an agent-owned task in `in_progress` or `in_review`; an authorized human may request one check of a retained monitor on a `blocked` task through normal admission without changing its status or bypassing dependency, recovery, ownership, pause or budget gates; provider-quota recovery monitors remain paused until the task resumes; a skipped manual dispatch returns a conflict instead of claiming success -- unmanaged shell jobs, detached sessions, adapter child processes, local polling loops, PIDs, logs, and comments are evidence rather than liveness; a managed runtime service counts only when paired with a persisted monitor, wake, blocker, or delegated issue that owns the next check -- heartbeat finalization evaluates liveness from persisted Paperclip state; an issue cannot remain healthy `in_progress` solely because the exiting heartbeat started a local/background watcher -- a continuation cancelled as `issue_continuation_waiting_on_review` first converts a current typed wait target into a first-class wait; without a current target it is classified as `deliberate_wait_without_target` and gives the invokable original owner five normal-model disposition-repair attempts (immediate, then after 60, 120, 240, and 480 seconds, with up to 10 percent jitter) -- disposition repair revalidates blockers, children, interactions, approvals, monitors, execution stages, queued wakes, active runs, work products, owner invokability, budgets, and governance before every attempt; the attempt bound is keyed by durable source state, so comments or equivalent parked prose do not reset it while durable source-state changes may establish a new fingerprint -- exhausted disposition repair appears in both task threads as “Agent needs attention,” with recorded attempt details and an explicit retry action; display uses structured recovery evidence, and the server rechecks the exact action, current task state, original owner, pause/approval/dependency/budget gates before restoring the task to todo -- backwards-compatible upgrades count consecutive historical `issue_continuation_waiting_on_review` cancellations for the unchanged accepted-interaction source state against the same five-attempt disposition-repair ceiling; missing pre-upgrade recovery-action rows do not reset the budget -- the source fingerprint, source-attempt count, next due time, source owner, and return owner persist in the recovery action; startup and periodic reconciliation resume that exact lineage without duplicate wakes, fold it when a current typed wait appears, and reschedule or escalate an expired action that has no live scheduled run -- source-attempt exhaustion opens one board-owned source-scoped recovery action without changing the source assignee and without waking a substitute agent; the board explicitly chooses whether to repair, retry the original owner, reassign, or resolve -- an active recovery action counts as a live source or blocker-chain path only while its owner has a live run, queued wake, scheduled retry, typed wait, or explicit board escalation; source and blocker projections consume the same nested recovery-path result -- when Paperclip cannot safely infer the next action, it surfaces the problem through visible blocked/recovery work instead of silently completing or reassigning work -- explicit recovery actions are the liveness primitive; source-scoped actions are the default form, issue-backed recovery is a fallback for independent repair work or safety boundaries, and comments alone are evidence rather than a healthy liveness path -- recovery-action ownership is separate from source-task ownership: automatic repair and board escalation preserve both source assignee fields; reassignment requires an explicit board decision or a policy-defined serious failure -- source-scoped recovery routing is cause-keyed: bounded continuity and disposition repair may retry only the original agent; provider-quota failures create/reuse a scheduled wait-recovery monitor; every other exhausted or unsafe path creates/reuses a board-owned recovery action with `routingPolicy: board_escalation_no_takeover_v1` and no substitute-agent wake -- legacy active agent-owned recovery actions remain readable, resolvable, and API-compatible after upgrade, but reconciliation does not enqueue another takeover wake for them -- active-run output silence is an informational board UI signal at five minutes (`suspicious`) and fifteen minutes (`critical`); it does not create or update issues or recovery actions, comment on or block source work, change assignments, or wake an agent -- board snooze and continue decisions suppress the run signal until their stored re-arm time; a false-positive decision suppresses it permanently for that run; open legacy evaluation issues remain readable and manually resolvable without automatic refresh - -Detailed ownership, execution, blocker, active-run watchdog, crash-recovery, and non-terminal liveness semantics are documented in `doc/execution-semantics.md`. - -For native ordinary tasks, answering a Board comment does not authorize an -indefinite response wait when the structured result reports blocking remaining -work. Without a recorded wait condition, reject the finish report or use the -bounded incomplete-work recovery path. Preserve real governance and pause gates, -conversation lifecycles, and protection against replaying superseded requests. - -## 8.3 Approval Status - -- `pending -> approved | rejected | cancelled` -- terminal after decision - -## 9. Auth and Permissions - -## 9.1 Board Auth - -- Session-based auth for human operator -- Board has full read/write across all companies in deployment -- Every board mutation writes to `activity_log` - -Human invitations default to the Operator role. Its default grants allow agent -creation and configuration, skill editing, environment management, invitations, -task assignment, connection and tool management/use, and tool -and agent-action audit views. Operators do not receive `joins:approve` or -`users:manage_permissions`. Explicit invitation grants remain authoritative. -Creating a human invitation also requires any of these two membership powers -included in its selected role. Operators can invite Operators and Viewers; -inviting an Admin requires join approval, and inviting an Owner also requires -member-permission management. -This preset change adds no database migration; existing role-default seeding -continues to insert missing grants without replacing custom scopes. - -## 9.2 Agent Auth - -- Bearer API key mapped to one agent and company -- Agent key scope: - - read org/task/company context for own company - - read company-visible tasks and comments - - comment on and update visible tasks under the shared write rule - - create child tasks and assign visible work for delegation under the same rule - - report heartbeat status - - report cost events -- Agent cannot: - - bypass approval gates - - modify company-wide budgets directly - - mutate auth/keys - -## 9.3 Permission Matrix (V1) - -| Action | Board | Agent | -|---|---|---| -| Create company | yes | no | -| Hire/create agent | yes (direct) | new standard agents: direct via `canCreateAgents`; low-trust policy or approval gates can restrict | -| Pause/resume agent | yes | pause: no; resume: direct `agents:configure` grant only | -| Create/update task | yes | yes | -| Force reassign task | yes | limited | -| Approve strategy/hire requests | yes | no | -| Report cost | yes | yes | -| Set company budget | yes | no | -| Set subordinate budget | yes | yes (manager subtree only) | -| Manage responsible user's inbox state | yes | yes (default-open policy) | -| Manage another user's inbox state | yes | saved target-user opt-in or scoped `inbox:manage` grant | -| Set work-object visibility (issue/project) | no | no (pro gate) | - -Agent resume is the only grant-gated exception in the lifecycle-route group. An -agent actor calling `POST /agents/:agentId/resume` must pass the protected -`agent_config:update` decision with `scope.requiresChangeGrant: true`; self -access does not bypass that decision, and `agents:suggest-changes` alone cannot -apply the lifecycle change. Pause, clear-error, terminate, approval, and -key-management routes remain board-only. - -An ordinary standard agent receives a direct `agents:configure` grant on -creation. Existing agents keep their current permissions; no backfill runs. -Low-trust and managed built-in agents do not receive this default. See -[agent permission defaults](agent-permission-defaults.md) for the full inventory. -Agent-authenticated changes cannot set or restore host-executed process adapter -configuration, including commands and environment values. Agent-authenticated -rollbacks cannot restore host-executed workspace commands either. - -### Individual agent interaction restrictions - -The optional `agents.permissions.interactions` object holds independent incoming -and outgoing controls for assignments, mentions, comments, task edits and peer -configuration, plus a separate outgoing assignment-to-humans control. Absence -adds no restriction; explicit false cannot be bypassed by grants or agent roles. -Human permission managers alone write the subtree through the dedicated API/CLI. -No migration, default write, grant backfill or UI is introduced. Mutation and -pending-delivery authorization use current settings while preserving history and -running work. See [the interaction permission contract](agent-interaction-permissions.md) -for the settings, ownership rules, atomic updates and compatibility boundaries. - -### 9.3.1 Shared default-open issue writes - -For standard-trust agents, issue comments, issue field/status updates, child -creation under a parent, and assignment share one authorization rule: the -target issue must be visible to the agent and the responsible user represented -by the run must also be authorized. In V1, issue visibility defaults to the -whole company, so these writes are company-wide by default. - -The shared rule does not widen low-trust, `skill_test`, or `task_bridge` key -scopes. It also does not replace run-lifecycle controls: checkout ownership, -active-run conflicts, status-transition validation, interaction ownership, -budget gates, and pause gates remain independently enforced. Comment access is -structurally downstream of issue read access (`issue:comment` is a subset of -`issue:read`). - -Low-trust agents may create self-assigned tasks and subtasks inside their existing -project or root-task scope. Both creation endpoints enforce task-assignment -authorization, including responsible-user and protected-assignment checks, even -when the new task is unassigned. Created tasks retain the effective containment -policy and quarantined source attribution. Self-assigned decomposition does not -count as delegation back to another agent. Persistent instruction changes remain -restricted except for self-edits requested through authenticated owner chat. -That exception requires the current accepted identity and a recorded direct -board-message wake, and rechecks the user's current instruction-editing permission -when saving. It does not extend to outside work, subtasks, peer instructions, or -other privileged configuration. Denials name the specific restriction. - -Authenticated board direction permits a low-trust agent to execute its own -human conversation or the exact task explicitly assigned or addressed by a human. -This server-owned exception is bound to the current assignee and the run's task; -it uses existing conversation identity and authenticated execution-request records, -including same-task retry ancestry. It does not expand inherited boundaries or -grant privileged tools. Backlog assignments retain the existing human requester -without starting a run. Reassignment transactionally cancels prior human requests, -including service/plugin writes; cancelled runs cannot authorize later retries. See `doc/LOW-TRUST-PRESETS.md` for containment details. - -Project or repository access is a separate boundary from exact-task direction. -A workspace project denial appears on the task with a link to the agent's -Permissions / Trust settings. Board operators with `agents:create` can add -same-company projects through `POST /api/agents/:id/project-access`; the operation -locks the agent, preserves existing permissions and audits added projects. -Saving does not retry work. The task offers explicit Retry after the required -projects are allowed, and dispatch rechecks authorization. Readiness checks the -task's current organizational project and selected workspace source, including -a valid pending selection. Moving the task to an allowed project does not clear -a retained source's access requirement. Existing setup failures with the exact -project-boundary denial also receive the explanation on reload, without assuming -that the task's current project caused the historical failure. A stale or -unavailable workspace selection stays in the non-retryable failure state; it -does not ask the user to grant project access that cannot repair the selection. - -Cross-issue writes are contained per heartbeat run. An agent-authored comment -may wake the target assignee, including an explicit `resume: true` comment on a -`done` or `cancelled` issue, but the wake remains agent-class and is subject to -the normal agent rewake throttle; comment presentation cannot give it human -wake privileges. Agent issue comments and updates require a persisted heartbeat -run bound to the authenticated agent and company; missing, invalid, or mismatched -run context fails closed before mutation. A run may attempt at most 20 cross-issue comments, issue -updates, or issue-thread interaction resolutions across one shared counter. The -server records each attempt with its source issue, target issue, run, count, and -rollout mode, and fails closed with the cap in the error once enforcement is -active. Writes to the run's own source issue are not counted. Assignee self-comments do not -wake the assignee, and a non-assignee comment cannot mint a mention grant. - -Agent @-mentions are context links only: they do not wake the mentioned agent, -assign work, or forward comments to another task. Normal comment feedback still -routes to the current assignee. Work for another agent requires explicit -assignment, delegation, or a review request. - -Agent-authored issue comments persist the responsible user derived from the -authenticated actor; clients cannot choose that attribution. Each comment also -records the write-policy reason, and spoof attempts fail with an audited 422. -Every issue PATCH emits an `issue.updated` activity receipt containing the -actor, responsible user, run, authorization reason, and field-level before/after -changes so both agent and board edits are visible in the issue activity stream. - -## 9.4 Permission Terminology and Default Visibility Rule - -Paperclip keeps company-scoped visibility as the default. The task-ACL foundation adds opt-in private issues while preserving `open` for all existing and unspecified issues. - -The approved term set is: - -- **Agent profile visibility**: identity-level facts needed for delegation and governance (name, role, capabilities, reporting lines). -- **Agent config visibility**: adapter/runtime config metadata and secret-access policy. -- **Assignment/invocation permission**: who may modify or execute a task. -- **Work-object visibility**: who can read/write issues, comments, projects, and attachments. -- **Tool/secret policy**: what tools and secret-backed credentials an agent can use and what appears in logs. -- **Escalation authority**: where refusal/blocked decisions route (manager, then board). - -## 9.5 Core V1 Rule: what “private” means - -- A **private marker** on an agent profile (where represented) does **not** make company-visible work private. -- Work objects remain company-open by default. An issue or project can opt into the shared private-read predicate. -- Organization-wide custom ACL policy remains deferred to Pro/Enterprise controls. - -## 9.6 V1 vs Pro/Enterprise Controls (recommended target split) - -| Permission area | Free / V1 default | Pro / Enterprise | -|---|---|---| -| Company boundary | Hard boundary only (`company_id`) | Multi-company policy overlays (`membership`, `project`, and `task` scopes) | -| Simple roles | Board + agent roles with existing approval/budget gates | Additional role aliases + scoped approver roles | -| Profile visibility | Full profile visibility for coordination and audit | Optional profile redaction / selective sharing for external surfaces | -| Config visibility | Board full read with redacted secret fields; agent config read/write constrained by own agent identity | Scoped config visibility controls and central policy enforcement | -| Assignment/invocation | Assignment creates execution authority; board can reassign or force release | Delegation policies and scoped invokers with deny-listed tool classes | -| Work-object visibility | Company-open by default; opt-in private issues/projects with explicit user or agent access | Policy-driven ACLs and reviewer-only channels | -| Tool/secret policy | Secret refs, log redaction, and adapter-level command/webhook restrictions | Tool allowlists with centralized policy evaluation | -| Company skills | Open to authenticated company agents; core enforces invariants and any stored restriction policy | Paperclip EE policy editor, protected-skill controls, presets, simulation, and policy audit UX | -| Inbox management | Responsible agent may archive/unarchive its responsible user's Mine items under a default-open user policy; explicit cross-user access requires saved target-user opt-in or `inbox:manage`; all mutations are audited | Policy administration UX, organization presets, simulations, bulk controls, and richer audit/reporting surfaces | -| Escalation | Escalate from agent to manager to board; board approval/budget gates remain authoritative | Escalation routing and SLA windows | - -## 9.7 Recommended first-slice implementation order - -1. Lock route-level checks for existing company boundaries, actor extraction, and approval/budget gates. -2. Treat profile privacy as external-facing signal only; do not use it to hide company-visible work objects. -3. Enforce assignment/invocation coupling (`assignee`/`agent` checks, checkout semantics, invocation checks). -4. Standardize read-path redaction for secrets and secret references, including logs and activity. -5. Standardize escalation paths (`blocked` and refusal) so non-board agents hand off by manager/board with immutable audit. - -## 9.8 Scoped Task Assignment Grants - -`tasks:assign` remains the broad assignment permission. Existing unscoped grants preserve compatibility and allow the principal to assign any visible company task within normal company-boundary checks. - -`tasks:assign_scope` is the constrained assignment permission. Its `principal_permission_grants.scope` JSON must include at least one recognized constraint: - -- Project scope: `projectId`, `projectIds`, or `allow: ["project:"]`. -- Target-agent allowlist: `agentId`, `agentIds`, `assigneeAgentId`, `assigneeAgentIds`, `targetAgentId`, `targetAgentIds`, or `allow: ["agent:"]`. -- Managed-subtree scope: `managerAgentId`, `managerAgentIds`, `managedSubtreeAgentId`, `managedSubtreeAgentIds`, `subtreeAgentId`, `subtreeAgentIds`, `subtreeRootAgentId`, `subtreeRootAgentIds`, or `allow: ["subtree:"]`. - -When multiple constraint families are present, assignment must satisfy all of them. Denials return `403` with a generic scope explanation and do not disclose details about hidden or unrelated resources. - -A protected-agent hard block is represented canonically as -`authorizationPolicy.protectedAgent.blockAssignment: true`. It denies assignment -even when the caller has a broad or scoped assignment grant. A company -administrator must remove the block before assignment can be retried; no pending -approval is created. The legacy fields `protectedAgent.requiresApproval` and -`assignmentPolicy.protectedAgentRequiresApproval` remain fail-closed compatibility -aliases for the same hard block, but API denial copy must describe the block and -administrator remediation rather than promising a nonexistent approval step. - -### 9.8.1 Issue-thread interaction resolver contract - -Issue-thread interactions are coordination records, not grants of authority. Every -interaction kind defaults to resolver policy `anyone` when the create request omits -`resolverPolicy`. Restrictions are opt-in. - -Question, confirmation, checkbox confirmation, and item verdict cards stay pending -when a user sends an ordinary task comment. Their `supersedeOnUserComment` flag -defaults to `false`. A creator may set it to `true` when a comment should replace -the pending request, as the opening onboarding question does. - -Canonical resolver policies are: - -- `anyone`: any authenticated actor in the interaction's company who can read the - issue and use the normal resolution route. For agents this includes the creator - agent and the creating/source run. -- `not_creator`: the explicit independent-review policy. It excludes the creator - agent and creating/source run while otherwise using the ordinary agent resolver - path. -- `human_only`: only an authorized human/board actor may resolve the interaction. - -`board_or_agents` and `board_only` are deprecated migration and API-input aliases. -For new writes they normalize to `anyone` and `human_only`, respectively. API reads -return canonical requested/effective policies, compatibility aliases, and immutable -provenance. The persisted provenance is `explicit`, `inherited`, or -`legacy_inherited_restriction`; the effective-policy source is `requested`, -`company_cap`, or `governed_action`. - -Historical rows predate explicit-vs-inherited provenance. Migration must never -silently widen an ambiguous pending card: legacy `board_or_agents` rows retain the -old creator-excluding behavior as canonical `not_creator`, legacy `board_only` rows -become `human_only`, and both are marked `legacy_inherited_restriction`. Resolved -outcomes and resolver attribution are immutable. - -An explicit named agent or user addressee and a company-configured cap may narrow -the effective audience. Only the exact named addressee may resolve an addressed -interaction; a human does not override a user addressee. Explicit user recipients -must exist and be authorized to respond in the issue's company before creation. -Ordinary Agent Chat questions derive their user recipient from the persisted -conversation owner. A conflicting explicit user recipient is rejected; IDs are -never guessed or repaired. Ordinary task questions keep optional addressing. -A cap never widens the requested audience. Tool-action confirmations and -other hard-governed action cards remain `human_only` (or move to the formal approval -system) regardless of a requested open audience. - -Surfaces that offer a resolution must state the effective audience before the -operator acts, from server metadata rather than a client-side policy inference. -Issue-thread cards read it from the interaction snapshot; attention rows read it -from the feed item's `resolverAudience` (canonical requested/effective policy, -effective-policy source, provenance, and the addressee/creator identities the -evaluator compares against), because a collapsed row carries decision verbs -before the interaction itself is fetched. A failed resolution keeps the server's -denial reason in visible, assertively announced feedback and names who may -respond; an audience denial is permanent, so it must not degrade to a retry -prompt. Neither surface may enable or disable a control on its own authority. - -Every resolution remains company-scoped, run-attributed for agent actors, -low-trust/task-bridge contained, target-current, and exact-once. Target staleness, -supersession, continuation idempotency, and activity attribution remain mandatory. -An open audience is not an uncapped one: when an agent run resolves an interaction -on an issue other than its own source issue, the resolution is a cross-issue -mutation and consumes the per-run cross-issue influence budget in §9.3, charged -after audience authorization and before the interaction mutation, child tasks, -continuation, tool action, or wake. Same-issue resolutions and board/user -resolutions are outside that counter. -Accepting or answering an interaction records a response only: suggested-task -creation, provider/tool calls, deployment, spend, hiring, secrets, execution-policy -decisions, and every other downstream effect must re-run its own authorization and -approval checks. Mislabeling a governed action as an open interaction grants no -downstream capability. - -## 9.9 Task Watchdog Authority Contract - -A task watchdog is a scoped execution capacity for a configured watchdog agent on one watched issue subtree. It is not a separate principal, does not inherit board auth, and does not expand the selected agent's company boundary. The server must enforce the watchdog contract from persisted watchdog configuration and run context; custom instructions and prompt text can narrow the mandate but cannot expand it. - -The watched subtree is the source issue plus descendants reached through `parent_id`, excluding every issue whose `origin_kind = 'task_watchdog'` and excluding all descendants below those watchdog issues. The generated reusable watchdog issue is outside the watched work subtree for scan purposes, but the watchdog agent may update that reusable watchdog issue to record its own review disposition. - -Task-watchdog wakes must include server-derived capability metadata that names the watched root, reusable watchdog issue, excluded `task_watchdog` origin branches, allowed operations, and denied operations. Watchdogs must use that metadata and server denials for capability discovery; they must not create visible probe issues, comments, or throwaway tasks to learn their permissions. - -### Allowed watchdog mutations - -Within the watched subtree, a watchdog run may perform only mutations that restore or clarify the next live/waiting path: - -- add comments that explain findings, evidence, and next action -- create descendant follow-up issues under an included subtree issue, inheriting company, project, goal, and workspace strategy from that subtree; each follow-up gets its own execution workspace -- assign or reassign included issues to active, invokable, same-company agents when normal assignment checks and scoped assignment grants allow it -- move included issues among `todo`, `in_progress`, `in_review`, and `blocked` when the transition is needed to restore a valid action path -- reopen `done` or `cancelled` included issues only with explicit resume metadata and an audit comment when evidence shows the stopped disposition is wrong or incomplete -- add, replace, or clear blockers on included issues when the blocker target is in the same company and the change makes the waiting path more accurate -- set or refresh a one-shot monitor on an included issue when the current assignee owns the future check -- resolve issue-thread interactions through the ordinary resolver-audience path - when the watchdog agent otherwise has issue access and the effective policy allows it -- update the reusable watchdog issue itself to `done`, `in_review`, or `blocked` with the evidence for the watchdog decision - -Every watchdog-triggered mutation must write activity with the watchdog id, source issue id, watchdog issue id when present, run id, and stop fingerprint. Mutations still use the normal status-transition, blocker, assignment, budget, and company-boundary guards. - -### Atomic recovery batch - -A watchdog run may submit an atomic recovery batch of at most 3 mutations drawn from the allowed-mutation list above, validated against the stop fingerprint that run observed. The server applies the batch all-or-nothing: if the subtree's stop fingerprint changed between observation and application — the subtree went live concurrently — the entire remainder of the batch is aborted and the staleness is recorded as evidence on the reusable watchdog issue. The batch is single-shot per watchdog run. This replaces the exactly-one-fresh-write model: the stale-guard's purpose (never mutate a subtree that concurrently went live) is preserved by fingerprint validation on the whole batch rather than by capping the run at one write, so a restoration that needs both a state-restoring `PATCH` and an explanatory comment cannot forfeit the restoration by ordering the comment first. - -### Restoration verification and escalation - -Reviewed-fingerprint suppression is disposition-aware. A watchdog disposition of "stopped state is legitimate" suppresses re-fire for that fingerprint as today. A disposition of "live path restored" arms a bounded verification instead: - -- if a later scan observes the subtree stopped with a fingerprint equal to the one the restoration claimed to fix, the watchdog re-fires with an incremented attempt count for that fingerprint lineage -- restoration-attempt lineage (attempt number, claimed-fixed fingerprint, restoration actions) is persisted durable watchdog state -- the stop fingerprint (or the lineage check) must account for intermediate-node durable updates so a restoration that changes no stopped leaf is classified as a failed restoration, not as a reviewed stop -- after N attempts (N = 2–3, configuration-bounded) on the same fingerprint lineage, the platform stops re-firing and escalates to a human — the watchdog owner or a board notification — with the attempt history attached - -Escalation is terminal for the automatic loop: no further watchdog wakes fire for that lineage until a human or a new durable subtree change produces a different fingerprint. - -### Disallowed watchdog mutations - -A task watchdog must not: - -- mutate issues outside the watched subtree, except for comments or newly created follow-up issues that are children of included subtree issues -- mutate company, project, goal, agent, auth, API key, budget, secret, environment, plugin, or deployment settings -- approve or reject rows in the `approvals` table, including hiring, CEO strategy, spend, budget override, or `request_board_approval` decisions -- resolve execution-policy decisions unless the watchdog agent is the typed participant under that policy outside of its watchdog capacity -- force-release checkout/execution locks, cancel active runs, terminate processes, or perform active-run output watchdog decisions -- create visible probe issues, comments, or throwaway tasks to discover whether an operation is allowed -- delete issue documents, comments, attachments, work products, or activity records -- change the watchdog configuration, select a different watchdog agent, or create nested watchdog configurations -- treat custom instructions as authority to bypass approval gates, cross company boundaries, access secrets, or override this contract - -When the safe next action needs one of these disallowed mutations, the watchdog must leave a valid waiting path by commenting, creating an in-subtree escalation/follow-up issue, assigning to the correct owner, or leaving the source issue blocked on a first-class blocker. - -### Interaction resolution - -A task-watchdog run has no special resolver audience, plan-purpose marker, or -interaction-kind allowlist. The task-watchdog context neither widens nor -categorically removes the selected agent's ordinary interaction authority. The -same evaluator used for every agent applies `anyone`, `not_creator`, `human_only`, -named-addressee, company-cap, company-boundary, run-attribution, low-trust, -task-bridge, target-staleness, and exact-once checks. - -Resolving an interaction does not authorize its downstream effect. In particular, -an accepted plan still passes normal decomposition/idempotency checks, and a -governed action still requires its own typed reviewer, permission, or formal -approval. A watchdog may not use an open coordination response to bypass any item -in the disallowed-mutations list above. - -### Downstream acceptance criteria - -Implementation, security, UI, and QA work for task watchdogs must prove these contract points: - -- server tests deny cross-company watched issues, watchdog agents, watchdog issues, blockers, interactions, and assignment targets -- server tests deny paused, terminated, pending-approval, budget-blocked, or otherwise uninvokable watchdog agents -- watchdog-scoped mutations can touch only the watched subtree and the reusable watchdog issue, with activity records for each mutation -- interaction tests prove watchdog runs use the same resolver policy as ordinary agents, without a watchdog-only kind or purpose-marker exception -- interaction tests cover `anyone`, `not_creator`, `human_only`, named addressees, company caps, stale targets, governed actions, newer user comments, low-trust/task-bridge containment, and cross-company denial -- scheduler tests prove live runs, queued wakes, and scheduled retries suppress watchdog wakeups, while terminal, cancelled, blocked, and review leaves are still verified when the subtree has no live path -- tests prove `task_watchdog` origin issues and descendants are excluded from scans so watchdogs do not trigger themselves -- recovery-batch tests prove batches are capped at 3 allowed mutations, applied all-or-nothing, and aborted with recorded evidence when the observed stop fingerprint went stale mid-batch -- restoration-verification tests prove a "live path restored" disposition re-fires on an unchanged fingerprint with an incremented attempt count, a failed intermediate-node restoration is not treated as a reviewed stop, and the N-attempt bound escalates to a human with attempt history instead of firing forever -- regression tests prove watchdog capability discovery comes from wake metadata/denials and denied probes do not create visible issues -- UI copy and badges distinguish task watchdogs from active-run output watchdogs, monitors, reviewers, approvers, and liveness recovery -- prompt/context tests prove custom instructions are appended after non-overridable safety constraints and cannot expand authority -- QA validates a full create/edit/remove/run/reuse flow with screenshots for UI changes - -No unresolved policy decision blocks implementation once CTO and Security accept this contract. Deliberately deferred and disallowed for the first implementation: letting watchdogs cancel active runs, approving board/governance actions, mutating outside the watched subtree, or allowing watchdog agents to modify their own watchdog configuration. Any expansion of those capabilities requires a new product/security review. - -## 9.10 Company Skill Policy Contract - -### Product default - -An authenticated agent may perform normal company-skill work without a skill-specific grant when the target company has no explicit skill policy. This includes creating, importing, installing, editing, updating, testing, resetting, and removing skills. Core MUST NOT introduce a `skills:author` prerequisite, a draft-only default, or an activation-approval default. - -Authorization order is fixed: - -1. Enforce non-configurable platform invariants. -2. Evaluate the company's explicit skill policy when one exists. -3. Otherwise allow the authenticated company agent. - -Non-configurable invariants include authenticated actor identity, exact company scoping, source and workspace path containment, package and frontmatter validation, secret redaction/non-export, immutable audit attribution, and any hard runtime isolation rule. A policy rule, legacy grant, plugin, or EE configuration cannot override these invariants. - -For avoidance of doubt: - -- Local-path imports, updates, resets, and project scans MUST resolve under a Paperclip-known local workspace root or a Paperclip-managed skill root. Arbitrary host filesystem paths are invalid even when the caller is otherwise authorized. Caller-supplied `source`, `sourceLocator`, or similar path strings are descriptive input only; they MUST NOT expand authority beyond those approved roots. -- Remote imports and updates MUST normalize to a known source category, require validated HTTPS or catalog sources, and resolve immutable content before install (for example pinned Git commit/content hash or pinned package version). Unknown schemes, unknown source categories, symlink escapes, and out-of-tree files fail closed before persistence. -- Unsafe executable content, fetch-and-exec patterns, and secret exfiltration or non-redacted secret material are platform safety failures. Policy cannot waive them; the route MUST reject the operation before any new skill version, install, update, or reset is persisted. -- Mandatory activity attribution is part of the invariant boundary. If the required audit record for a skill mutation or policy mutation cannot be persisted, the mutation MUST fail or roll back; do not return success with missing auditability. - -### Canonical actions and resources - -The version 1 evaluator uses these stable action identifiers: - -- `skills.create`: create or fork a company-authored skill and create skill versions -- `skills.import`: import or scan skills from a workspace, Git source, URL, or package -- `skills.install`: install a catalog or externally sourced skill into the company -- `skills.edit`: change skill metadata, name, files, test inputs, or test templates -- `skills.update`: install a newer upstream revision -- `skills.test`: start, cancel, or remove a skill test run and run a skill audit -- `skills.reset`: restore the installed/upstream revision -- `skills.remove`: delete a company skill - -Policy resources may include `skillId`, stable `skillKey`, `sourceType`, and `sourceLocator`. The stable source categories are `workspace`, `catalog`, `git`, `external_package`, `generated`, and `unknown`; adapters may preserve a more specific source value as metadata, but policy evaluation MUST normalize it to one of these categories. Core derives actor and company identity from authentication and derives known resource fields from stored data; a mutation client cannot authorize itself by supplying actor or resource identity fields. - -### Version 1 policy document - -Absence of a policy record is semantically equivalent to the following document, but core SHOULD avoid materializing records for untouched companies: - -```json -{ - "schemaVersion": 1, - "revision": 0, - "defaultEffect": "allow", - "rules": [] -} -``` - -An explicit policy has a monotonically increasing `revision`, a `defaultEffect` of `allow` or `deny`, and ordered rules. Each rule contains a stable `id`, integer `priority`, `effect` (`allow` or `deny`), a subject selector (`all_agents`, agent ids, or role names), one or more canonical actions, and optional resource selectors for skill ids/keys and normalized source types/locators. An omitted resource selector matches every resource for the listed action. - -Rules are evaluated by ascending `priority`, then stable rule id; the first matching rule decides. If no rule matches, `defaultEffect` decides. This supports both the normal open policy with targeted deny rules and an opt-in restricted preset with default deny plus explicit allow rules. Core MUST validate policy documents atomically and reject ambiguous, unknown-version, unknown-action, cross-company, or malformed selectors with `422`. - -Every decision returned by the evaluator has this stable shape: - -```json -{ - "allowed": false, - "action": "skills.install", - "reason": "explicit_rule", - "policyRevision": 7, - "matchedRuleId": "deny-external-packages", - "remediation": "Contact a company administrator to change the skill policy." -} -``` - -`reason` is one of `platform_invariant`, `no_policy_default`, `explicit_rule`, `policy_default`, or `legacy_compatibility`. Mutation routes MUST use this evaluator and return `403` with code `skill_policy_denied` and the non-sensitive decision fields when an explicit restriction denies an operation. Denials must identify the action and remediation without exposing hidden rule data, secrets, or another company's policy. - -Platform-invariant failures are not policy denials and MUST use stable machine-readable error codes so clients can distinguish non-overridable safety failures from optional administrative restrictions. Version 1 requires a finite code set covering at least: - -- `skill_authentication_required` -- `skill_company_boundary_denied` -- `skill_workspace_boundary_denied` -- `skill_source_validation_failed` -- `skill_unsafe_content_blocked` -- `skill_secret_handling_blocked` -- `skill_policy_admin_required` - -Core Skill Studio and Paperclip EE MUST treat those codes as hard platform failures, not as prompts to loosen policy. - -### Core API and ownership boundary - -Core owns and ships these company-scoped endpoints: - -- `GET /companies/:companyId/skill-policy` returns the effective versioned policy, its revision, and whether it is materialized or the open default. -- `PUT /companies/:companyId/skill-policy` atomically replaces the policy and requires the caller's expected revision; stale writes return `409`. -- `DELETE /companies/:companyId/skill-policy` removes explicit configuration and restores the open default. -- `POST /companies/:companyId/skill-policy/evaluate` simulates decisions for administrative tooling without performing a skill mutation. - -Policy reads, writes, deletion, and simulation enforce company access. Policy mutation and cross-principal simulation require board administration authority or the existing `users:manage_permissions` capability; ordinary skill access does not. Every policy mutation writes an activity event containing the actor, previous revision, new revision, and a redacted change summary. Skill mutation activity logging remains required independently of the policy decision. - -Paperclip EE owns the detailed editor, presets, protected-skill management, policy simulation UX, and policy-specific audit views. EE consumes the core endpoints and does not implement a second evaluator. Core may expose a concise effective-policy summary and denial state, but MUST NOT depend on EE for enforcement or make EE installation a prerequisite for normal skill work. - -### Compatibility and availability - -- Existing companies with no explicit restriction adopt the open default, including companies that previously depended on missing grants to deny skill changes. Release notes and upgrade guidance MUST call out this behavior change. -- Existing explicit restriction policies remain effective after migration. -- Legacy `skills:create` and `skills:suggest-changes` positive grants remain accepted in APIs and portability packages. Historically either positive grant authorized the broad company-skill mutation surface, so in an explicit restricted policy either grant remains a compatibility allow fallback for all eight canonical skill actions only when no explicit rule matched. They never override an explicit deny or a platform invariant. With no explicit policy they are redundant because the default already allows the action. -- Legacy `skills:suggest-changes` consent state is not a platform invariant for company skills and does not add a second mutation gate under the open-default policy. Companies that require approval or consent before skill changes must express that restriction through explicit skill-policy rules; authentication, company boundaries, source containment, validation, auditability, and runtime safety remain non-configurable invariants. -- Import preview MUST report whether a package contains an explicit skill policy or legacy grants and how each will map. Import apply MUST preserve explicit policies, normalize supported legacy grants, and reject unknown policy versions rather than silently weakening them. -- Export MUST include explicit skill policy configuration and retained legacy grants in `.paperclip.yaml`, never secret values or environment-specific paths. An unconfigured company exports no synthetic restriction. -- If Paperclip EE is unavailable or removed, core continues to enforce stored policies and expose the policy API. Normal skill work remains available under the open default; explicit denials use core remediation text rather than a broken EE-only link. - -### Required regression tests - -Phase 2 server tests and Phase 4 UI tests must prove: - -- unauthenticated actors and authenticated actors from another company are denied for all skill mutation routes and all skill-policy routes -- local-path imports and project scans reject paths outside approved workspace or managed-skill roots, including symlink escapes and out-of-tree files -- remote imports and updates reject unknown schemes/categories, unpinned mutable refs, unsafe executable content, and secret exfiltration patterns before persistence -- policy mutation, policy reset, and cross-principal policy simulation require board administration authority or `users:manage_permissions`; ordinary open-default skill access never grants those actions -- explicit policy denials return `skill_policy_denied`, while platform safety failures return the stable invariant denial codes above -- successful skill mutations and policy mutations persist activity records with actor, company, run attribution, normalized action, and revision/change summary; audit-write failures do not leave successful unaudited mutations behind - -## 9.11 Inbox Management Permission and Ownership Contract - -`inbox:manage` is the permission key for agent-driven per-user inbox archive state. Inbox archive state changes presentation in a user's Mine inbox; it does not change issue status, assignment, visibility, or the underlying work record. - -Core authorization follows these rules: - -- Board users may archive or unarchive inbox entries for users in the company. -- An agent may manage the responsible user's inbox without an explicit grant when the authenticated run resolves that user and the user's inbox-agent policy permits the agent. This is the default-open path. -- A user may set inbox-agent policy to `disabled` or `allowlist`. Policy restrictions override the default-open path, and low-trust agents are denied. -- An agent targeting any user other than its resolved responsible user requires either a materialized target-user policy that permits that agent (`open` or matching `allowlist`) or an explicit `inbox:manage` grant. The implicit default-open policy for a missing row remains responsible-user-only, so it never becomes a blanket cross-user grant. Grants may be unscoped or constrained by `scope.userIds` and act as administrative overrides, including over a disabled target-user policy. -- Archive and unarchive operations are company-scoped, reversible, and activity logged with actor, agent, run, target user, target-resolution source, and policy mode. -- Concurrent archives keep the newest archive time and its actor attribution. An earlier request must not undo an archive written by human completion while it waited. Unarchive removes that state so a later archive starts fresh. -- New qualifying issue activity may invalidate an archive so the item resurfaces; archival is not a substitute for resolving or closing work. -- Viewing an issue may update its per-user read receipt, but read receipts alone do not enroll the issue in Mine. Mine participation begins with direct human creation of an ordinary task, human assignment, a direct board/API comment, or another deliberate audited user mutation. Open requests addressed to the person also admit the task until resolution. Channel and routine creator attribution, imported comments, and manual routine runs do not establish participation. - -Failed-run rows in Mine and the inbox badge use the run's `responsibleUserId`. -Another user's run does not appear there, even for a shared agent. Select the -latest run per agent before checking ownership so older failures do not resurface. -Unattributed runs appear only for `local-board` in Mine; an unresolved viewer -identity shows no failed runs. All retains company-wide failed-run visibility. -Company health alerts do not contribute to the personal inbox badge. -Run list responses, including summaries, retain `responsibleUserId`. - -Live task and run toasts require personal task participation or an explicit user -mention, independently of read access. Agent ownership and responsible-user -attribution alone do not qualify. See [toast semantics](TOASTS.md). - -Ownership split: - -- **Core / Free:** permission key and scoped-grant enforcement; responsible-user resolution; default-open, disabled, and allowlist policy modes; archive/unarchive APIs; per-user archive persistence; resurfacing behavior; activity audit records; and stable denial codes. -- **Paperclip EE / Enterprise:** centralized policy administration beyond the per-user controls, organization-wide presets, policy simulation, bulk inbox operations, advanced compliance reporting, and richer administrative audit UX. EE may extend policy management surfaces but must not weaken core company boundaries, user policy restrictions, scoped grants, or audit requirements. - -## 10. API Contract (REST) - -All endpoints are under `/api` and return JSON. - -## 10.1 Companies - -- `GET /companies` -- `POST /companies` -- `GET /companies/:companyId` -- `PATCH /companies/:companyId` -- `PATCH /companies/:companyId/branding` -- `POST /companies/:companyId/archive` - -On a Paperclip Cloud-managed instance, `POST /companies` returns `403` with -code `cloud_managed`; the trusted-header provisioning path and company import -routes remain the only company-creation paths there. - -## 10.1.1 Organization navigation and Cloud invitations - -Core's built-in organization switcher lists this instance's companies. An -installed plugin can replace it through the generic `organizationSwitcher` -slot. The built-in menu remains available when no unique usable contribution -exists. Core does not fetch or render Cloud portfolios in its switcher. Managed -hosts do not expose local company creation in that built-in menu; their extension -owns the creation action. - -The Cloud Members-page invitation action still uses the following route: - -- `GET /cloud/stacks` - -The route exists only on a Cloud-managed instance, requires a trusted -`cloud_tenant` actor, and proxies the current actor's user id plus the current -stack id to the Cloud tenant portfolio endpoint. Client-supplied user ids are -never forwarded. Successful responses are cached briefly per user; self-hosted -instances return `404`. - -On Cloud-managed instances, the Members page offers **Invite people** to the -current stack's owner/admin. It opens that stack's Cloud People settings with a -full-page navigation. The destination uses the configured Cloud origin and the -current stack from the authenticated portfolio. The action stays hidden until -that role is known, or when the portfolio request fails. Hiding the tenant-local -Invites tab does not hide this Cloud action. - -## 10.2 Goals - -- `GET /companies/:companyId/goals` -- `POST /companies/:companyId/goals` -- `GET /goals/:goalId` -- `PATCH /goals/:goalId` -- `DELETE /goals/:goalId` (soft delete optional, hard delete board-only) - -## 10.3 Agents - -Saving an existing agent's configuration with a healthy saved AI account checks -current account access, installation, provider/model compatibility, and environment -selection without allocating a computer or running a provider hello test. Use -**Test** to verify the runtime. Unverified legacy-account adoption, task connection -adoption, and Copilot's account-specific model checks retain their live validation. -A configuration save confirms persistence; it does not certify runtime readiness. - -The Harness / Runtime configuration page offers **Upgrade runner** for existing -legacy Claude, Codex, and Grok Build agents when the target environment selects Paperclip -Runner by default. The action uses the normal authorized agent PATCH with -`runner: "paperclip"`, includes pending configuration edits, and retains the -model and compatible settings. A rejected conversion leaves saved configuration -unchanged and identifies unsupported fields. Successful conversion records a -configuration revision, clears incompatible session state, and preserves agent -identity, instruction files, credentials, run policy, and run history. Existing -agents never upgrade merely by visiting or saving unrelated configuration. - -The Core setup wizard creates its agent record when the user submits the agent -name, before adapter selection, authentication, and runtime configuration. -Until the adapter is chosen, the held record uses a provisional adapter -(process with no command when enabled) and `metadata.setupAdapterPending: true`. Resuming such a record -returns to adapter selection with automatic runner selection. Choosing an adapter -updates the same record and clears that marker. Held Copilot records can select -the adapter before connecting an account; account checks still apply at connection -testing and final hire. Setup preserves server-assigned names, including numbered -duplicates. Name-only lookup does not adopt another unfinished record. `POST /companies/:companyId/agents` accepts a board-only `setupId` -(a UUID used as the agent ID). The record stays paused with a `setup` hold; -repeating that request reuses the same unfinished record. It starts no lifecycle -preparation and creates no hire approval. Instructions, task-assignment membership, -and creation activity are applied at final hire. Normal create requests keep their -existing behavior. - -The wizard saves configuration on that record through `PATCH /agents/:agentId`. -To finish, it submits the final configuration to -`POST /companies/:companyId/agent-hires` with `setupAgentId`. Core validates the -configuration and completes the same record. Companies that require approval -receive a hire approval for that final configuration; other agents enter normal -preparation and verification. A setup record cannot be resumed into execution, -receive API keys, or authenticate as an agent. Its standard grants apply at hire. -Authorized retries recover the completed record and its existing hire approval; -they cannot replace the submitted configuration. Both setup entry points save -configuration on the record so the roster can reopen it with the same runner. -Resuming setup loads the server configuration before autosaving local edits. -Its saved environment is used for login, connection verification, and final hire. -Changing harnesses drops incompatible saved AI connections. Setup runtime updates -carry the current connection choice rather than retaining a superseded binding. -Connection tests use the setup ID to restore redacted environment values under -the setup creator's permissions; shared connections are installed only at hire. -Changing Claude authentication modes removes the previous mode's credentials. -Held setup records may omit provider details such as the Copilot model; final -hire still requires a complete provider configuration. Kimi API model, protocol, -and endpoint choices are also stored as nonsecret `adapterConfig.kimiApi` settings -so they can be restored while environment values remain redacted. -Credentials remain available for retry when a save response is lost. A confirmed -client rejection removes only newly staged credentials from that attempt. -Sandbox authentication and saved AI connections keep their existing flows. - -- `GET /companies/:companyId/agents` -- `POST /companies/:companyId/agents` -- `GET /agents/:agentId` -- `PATCH /agents/:agentId` -- `POST /agents/:agentId/pause` -- `POST /agents/:agentId/resume` -- `POST /agents/:agentId/terminate` -- `POST /agents/:agentId/keys` (create API key) -- `POST /agents/:agentId/heartbeat/invoke` - -## 10.4 Tasks (Issues) - -- `GET /companies/:companyId/issues` -- `GET /companies/:companyId/issues/summaries?ids=,` -- `POST /companies/:companyId/issues` -- `GET /issues/:issueId` -- `PATCH /issues/:issueId` -- `GET /issues/:issueId/access-grants` -- `POST /issues/:issueId/access-grants` -- `POST /issues/:issueId/access-grants/:grantId/revoke` -- `GET /issues/:issueId/documents` -- `GET /issues/:issueId/documents/:key` -- `PUT /issues/:issueId/documents/:key` -- `POST /issues/:issueId/documents/:key/lock` -- `POST /issues/:issueId/documents/:key/unlock` -- `GET /issues/:issueId/documents/:key/revisions` -- `DELETE /issues/:issueId/documents/:key` -- `POST /issues/:issueId/checkout` -- `POST /issues/:issueId/release` -- `POST /issues/:issueId/admin/force-release` (board-only lock recovery) -- `POST /issues/:issueId/comments` -- `GET /issues/:issueId/comments` -- `POST /companies/:companyId/issues/:issueId/attachments` (multipart upload) -- `GET /issues/:issueId/attachments` -- `GET /attachments/:attachmentId/content` -- `DELETE /attachments/:attachmentId` -- `GET /issues/:issueId/runner-goal?agentId=...` -- `POST /issues/:issueId/runner-goal/actions` - -The runner-goal endpoints control an issue-scoped durable agent-session goal, -not a row in the company `goals` hierarchy. Reads return the effective agent, -negotiated capability, normalized goal snapshot, active-run state, pending -action, and revision. Mutations require a request id, assigned agent, expected -revision, and a negotiated action; they return `202`, replay the original result -for a duplicate request id, and return `409` with the current projection for a -stale revision or an unconfirmed unfinished-goal replacement. These controls do -not create issue comments. - -The task-summary endpoint accepts one to five comma-separated UUIDs, normalizes -case and whitespace, and removes duplicates after validating the five-entry -bound. It returns accessible, non-hidden tasks in request order with only ID, -company ID, title, identifier, status, update time, and external-conversation -state. Missing, foreign-company and unauthorized tasks are omitted. Company -access and normal task-read authorization apply; task-bridge keys are denied. -Only the `ids` query parameter is accepted. Malformed, repeated, nested, empty -and oversized ID parameters return `400`, including mixed scalar/nested syntax. -Responses are private and not stored by HTTP caches. Reads do not trigger -full-detail enrichment, workspace inspection or execution recovery. - -### 10.4.1 Atomic Checkout Contract - -`POST /issues/:issueId/checkout` request: - -```json -{ - "agentId": "uuid", - "expectedStatuses": ["todo", "backlog", "blocked", "in_review"] -} -``` - -Server behavior: - -1. single SQL update with `WHERE id = ? AND status IN (?) AND (assignee_agent_id IS NULL OR assignee_agent_id = :agentId)` -2. if updated row count is 0, return `409` with current owner/status -3. successful checkout sets `assignee_agent_id`, `status = in_progress`, and `started_at` - -`POST /issues/:issueId/release` clears checkout and execution locks. For terminal -issues (`done` or `cancelled`), it preserves the assignee, status, and disposition -timestamps, including on repeated release. For unfinished issues it clears the -agent assignee; only `in_progress` changes to `todo`. Existing company access, -assignee/run ownership checks, and the `issue.released` activity event still apply. - -A status change releases the checkout and execution locks but does not stop the -run that holds them. An agent request (`PATCH /issues/:issueId` or a recovery -resolution) that sets a status other than `in_progress` (even its current value) on -a task whose lock is held by a different queued or running run is refused with -`409` and code `issue_status_change_live_run`; -otherwise the run would keep going with no task bound to it, which is the effect -of interrupting another agent's run. The denial is final and names the person's -controls on the linked task: the run's Stop or Interrupt, or More task actions, -then Pause subtree on the parent. The lock-holding run, board users, and paths -that stop the run themselves (cancelling the task, reassigning it, and completing -through `PATCH` a task whose Runner goal is paused first) are unaffected. The write -is conditional on the lock the check saw; a run that acquires the lock in between returns `409` -`issue_execution_lock_changed`. - -`POST /issues/:issueId/admin/force-release` is an operator recovery endpoint for stale harness locks. It requires board access to the issue company, clears checkout and execution run lock fields, and may clear the agent assignee when `clearAssignee=true` is passed. The route must write an `issue.admin_force_release` activity log entry containing the previous checkout and execution run IDs. - -## 10.5 Projects - -- `GET /companies/:companyId/projects` -- `POST /companies/:companyId/projects` -- `GET /projects/:projectId` -- `PATCH /projects/:projectId` -- `GET /projects/:projectId/access-members` -- `POST /projects/:projectId/access-members` -- `DELETE /projects/:projectId/access-members/:memberId` - -Project list, search, direct reads, and nested project routes apply private-project access. Non-members receive the same not-found behavior as missing projects. - -## 10.6 Current-user Resource Memberships - -- `GET /companies/:companyId/resource-memberships/me` -- `PUT /companies/:companyId/resource-memberships/me/projects/:projectId` -- `PUT /companies/:companyId/resource-memberships/me/agents/:agentId` - -Request payload: - -```json -{ "state": "joined" } -``` - -Allowed states are `joined` and `left`. Endpoints require a concrete board user and active company membership, reject agent API keys, and only mutate the caller's own sidebar visibility state. Joining/leaving is idempotent; missing rows read as `joined`. - -## 10.7 Approvals - -- `GET /companies/:companyId/approvals?status=pending` -- `POST /companies/:companyId/approvals` -- `POST /approvals/:approvalId/approve` -- `POST /approvals/:approvalId/reject` - -## 10.8 Cost and Budgets - -- `POST /companies/:companyId/cost-events` -- `GET /companies/:companyId/costs/summary` -- `GET /companies/:companyId/costs/by-agent` -- `GET /companies/:companyId/costs/by-project` -- `PATCH /companies/:companyId/budgets` -- `PATCH /agents/:agentId/budgets` - -## 10.9 Activity and Dashboard - -- `GET /companies/:companyId/activity` -- `GET /companies/:companyId/dashboard` - -Dashboard payload must include: - -- active/running/paused/error agent counts -- open/in-progress/blocked/done issue counts -- month-to-date spend and budget utilization -- pending approvals count - -The dashboard agent cards show each linked task at most once. The server selects -distinct task cards from bounded active and recent run samples before it applies -the dashboard card limit. When multiple runs belong to one task, an active run -takes precedence over completed runs. -Runs without a linked task remain separate cards. The dashboard keeps the -count of additional distinct cards in its link to the live runs page, which -can show every run. - -## 10.10 Error Semantics - -- `400` validation error -- `401` unauthenticated -- `403` unauthorized -- `404` not found -- `409` state conflict (checkout conflict, invalid transition) -- `422` semantic rule violation -- `500` server error - -## 10.11 Current Implementation API Addenda - -The current app also exposes V1-supporting surfaces for: - -- company-scoped summary slots for projects, the workspaces overview, project workspaces, and individual execution workspaces; execution-workspace slots are keyed by execution workspace id so a new workspace never inherits another workspace's summary -- issue thread interactions (`suggest_tasks`, `ask_user_questions`, `request_confirmation`, `request_checkbox_confirmation`, `request_item_verdicts`) with the open-default resolver contract in §9.8.1 -- issue approvals, issue references/search, labels, read state, inbox/archive state, and work products -- task search uses shared PostgreSQL matching/ranking for company search and task-list quick search; - all query terms contribute, quoted phrases stay literal, exact identifiers and direct title matches - lead relevance ordering, and the UI preserves server result order (see `doc/SEARCH.md`) -- company search through `GET /companies/:companyId/search` plus agent-oriented bulk extraction through - `GET /companies/:companyId/search/extract`; extraction accepts a server-escaped literal `contains`, optional - server-owned URL expansion, issue/comment/document scopes, status/date filters, issue-level pagination, a - bounded `matchesPerIssue` override for machine consumers, and explicit issue/match truncation flags -- execution workspaces, project workspaces, workspace runtime services, and workspace operations. Workspace reads - return stored metadata without storage inspection; explicit readiness derives `deliveryState` as - `merged_via_pr | merged_by_ancestry | unmerged | unknown`. For providers without ancestry inspection, - automatic cleanup proves the finalized HEAD reached its captured destination through GitHub ancestry - or an exact-head merged PR, including squash merges. Recorded PR work products are optional hints. - Fresh storage, recoverability, and lifecycle checks remain mandatory. Terminal issue trees with a - merged delivery and no active checkout run become cleanup-eligible with reason `issue_terminal` and are archived - through the workspace cleanup path. Reopening the source issue records activity but does not restore that workspace. - Before that, terminal isolated worktrees are compacted: ignored, untracked dependency, build, and cache directories - are removed while the workspace, its branch, and all source changes stay, and reuse regenerates them. -- task watchdog configuration and reusable watchdog issue orchestration for explicitly watched issue subtrees -- routines and scheduled/API/webhook triggers -- plugin installation, configuration, state, jobs, logs, webhooks, and plugin database namespace migration -- company import/export preview/apply, feedback export/vote routes, instance backup/config routes, invites, join requests, memberships, and permission grants -- company skill policy read/replace/reset/simulation, enforced by the same core evaluator used by skill mutation routes -- decision queues and per-attention-item triage: - - `GET|POST /companies/:companyId/decision-queues` - - `PATCH /companies/:companyId/decision-queues/:key` - - `GET|POST /companies/:companyId/decision-queues/:key/items` - - `DELETE /companies/:companyId/decision-queues/:key/items/:sourceKind/:sourceId` - - `GET /companies/:companyId/decision-queue-seed-rules` - - `GET|PUT /companies/:companyId/decision-triage/:sourceKind/:sourceId` - - `PATCH /companies/:companyId/decision-retention/:sourceKind/:sourceId` (Keep) - - `POST /companies/:companyId/decision-retention/:sourceKind/:sourceId/archive|revive` - - `POST /companies/:companyId/decision-archive-proposals` - -Queue and triage mutations accept board non-viewers and active standard-scope agents, apply responsible-user intersection for run JWTs, and reject low-trust, `task_bridge`, and `skill_test` contexts. Missing, cross-company, and unauthorized attention sources share the same not-found response. - -The attention feed returns server-computed `shelf`, `retentionDays`, `keep`, `archivedAt`, and `retentionVersion` fields. Archived rows are excluded by default and selected with `archived=true`. Bulk archive proposals bind the exact source identities, per-item reasons, activity timestamps, and expected retention versions into the signed decisions-v1 target snapshots; acceptance re-authorizes both proposer and decider and commits all rows or none. - -## 11. Heartbeat and Adapter Contract - -## 11.1 Adapter Interface - -```ts -interface AgentAdapter { - invoke(agent: Agent, context: InvocationContext): Promise; - status(run: HeartbeatRun): Promise; - cancel(run: HeartbeatRun): Promise; -} -``` - -### Local adapter engine availability - -For the legacy Codex, Claude, Gemini, and Kimi local adapters, an omitted engine -or legacy `auto` value selects ACP deterministically. Missing prerequisites or -ACP execution failures fail the run; they must not launch a different engine -with different session, permission, or sandbox semantics. CLI execution requires -explicit selection. Environment tests report the same engine availability error -as execution. Codex CLI defaults permit workspace writes and network access for -Paperclip coordination without disabling its sandbox; explicit operator -restrictions and execution-target network denials remain effective. - -### Experimental personal Muse Runner provider - -The `paperclip_runner` adapter can select `provider: "muse"` behind the default-off -Muse and native Runner gates. This connects personal Meta Muse through a -company- and named-agent-bound receiver, distinct from human-authorized MCP. -Pairing, detector contact, and a verified background reply are separate setup -milestones. Idle first-party collaboration retains both agent and authorizing -user permissions; task execution additionally requires the admitted native run. -Only one assignment may execute per connection, and unresolved remote work or -native effects block overlap across cancellation, reconnect, and provider changes. -Remote interruption and external usage remain unconfirmed or unavailable unless -supported by evidence. Release requires the live journeys and bounded cadence -qualification in [Personal Muse receiver](muse-personal-agent.md). - -## 11.2 Process Adapter - -Config shape: - -```json -{ - "command": "string", - "args": ["string"], - "cwd": "string", - "env": {"KEY": "VALUE"}, - "timeoutSec": 900, - "graceSec": 15 -} -``` - -Behavior: - -- spawn child process -- stream stdout/stderr to run logs -- mark run status on exit code/timeout -- cancel sends SIGTERM then SIGKILL after grace - -## 11.3 HTTP Adapter - -Config shape: - -```json -{ - "url": "https://...", - "method": "POST", - "headers": {"Authorization": "Bearer ..."}, - "timeoutMs": 15000, - "payloadTemplate": {"agentId": "{{agent.id}}", "runId": "{{run.id}}"} -} -``` - -Behavior: - -- invoke by outbound HTTP request -- 2xx means accepted -- non-2xx marks failed invocation -- optional callback endpoint allows asynchronous completion updates - -## 11.4 Context Delivery - -- `thin`: send IDs and pointers only; agent fetches context via API -- `fat`: include current assignments, goal summary, budget snapshot, and recent comments - -## 11.5 Recovery Work Classes - -Status-only recovery coordination must include guard context that prevents deliverable work and document or plan updates (`allowDeliverableWork: false`, `allowDocumentUpdates: false`, `resumeRequiresNormalModel: true`). Recovery work classes do not select or change the agent model. - -Failed source-work retries, process-loss retries, transient or scheduled retries, max-turn continuations, source-assignee continuations, and downstream source-work child, requeue, or resume contexts use the agent's configured model. If status-only recovery repairs liveness while actual work remains, the next live continuation path must be a separate worker run. - -## 11.6 Scheduler Rules - -Per-agent schedule fields in `adapter_config`: - -- `enabled` boolean -- `intervalSec` integer (minimum 30) -- `maxConcurrentRuns` integer; new agents default to `20`; scheduler clamps configured values to `1..50` - -Scheduler must skip invocation when: - -- agent is paused/terminated -- an existing run is active -- hard budget limit has been hit - -Paid-operation settlement and receipt ingestion use operation-scoped transactions. -They do not acquire the company accounting mutex or update shared company/agent -spend counters. The ledger, reservation transition and accounting acknowledgement -commit atomically. Duplicate evidence uses stable identities; a conflicting -receipt is not another charge. Monthly and lifetime totals read the canonical -ledger and retained historical baselines. - -Admission reads current company, agent and project controls plus coherent budget -exposure. Reservations record an operation's estimated obligation, rather than -an exclusive claim on shared remaining capacity. Concurrent admissions may see -the same available balance and both start. Budget enforcement is best effort, -not a guaranteed bound on final billed spend. Manual Stop, lifecycle authority, -permissions and provider-dispatch ownership retain their final gates. - -Financial changes append independent durable evaluation inputs. The existing -worker coalesces notifications over one second and publishes budget holds, -incidents and durable Stop intent from a consistent snapshot. Policy/control -publication is fenced separately from settlement. Inputs arriving or committing -later stay pending; no global sequence watermark consumes them. Admission does -not wait for them: it reads the ledger and live exposure itself, so a reached -hard stop refuses new work even while evaluation lags. - -**Admission is independent of cleanup and accounting.** Admitting new work never -waits on another run's cleanup or accounting completeness. Only these refuse it: - -- spend at the limit (ledger plus observed running spend), or unpriced usage - a provider reported, under a blocking policy (the hard stop). A charge that - accounting settled without its receipt (lost, closure evidence that never - arrived, or a failed capture) is recorded and shown in Costs, but it is never - a company gate; -- an explicit pause of the company, project or agent; -- an inactive company. - -A run whose accounting is unfinished (its final receipt has not closed, its -native coordinator is still recovering, its usage capture failed, or a late -receipt is outstanding) contributes its bounded estimate to exposure: the -larger of its held reservation and its observed cost. It never creates a -company-wide or scope-wide gate, and it never pauses a scope or opens a -hard-stop incident. A budget-owned scope hold whose policies no longer reach a -hard stop is stale; the budget worker releases it, and budget admission does -not refuse work while it waits. Unevaluated budget inputs are not a gate. -Provider reporting and cancellation delays remain additional to evaluation lag. -Cancellation delivery is asynchronous and scoped; committed per-run Stop -survives policy changes and restart until existing stop proof succeeds. Budget -resume cannot clear a manual hold. - -A successor run on the same task still waits while its predecessor owns -resources that the successor would reuse (a live environment lease, an open -native finalization coordinator, or workspace finalization). That wait is -per task, ends when the owner releases them, and wakes the successor through -the work-intent promotion path. A predecessor run row that no longer exists -owns nothing and never holds its successor. - -**Accepted work intents are delivered exactly once.** An accepted work intent -(an assignment, a comment, a Retry or a recovery) ends in one of four terminal -outcomes only: delivered, consumed by a later run that received it, task -cancelled, or a reached hard stop. Every other refusal is temporary. The intent -is saved as a deferred wake whose `executionWait` names the refusal and the -event that clears it (a lifted pause, a released lock, settled accounting, a -released task-tree hold, or resumed scheduling). Each such event promotes -deferred intents through one promotion function, and the ownership reconciler -promotes any intent whose hold cleared without that event. Promotion re-runs -normal admission. Consumption is recorded per comment or intent id, so a -promotion never delivers an intent that a later run already received. - -Accepted cumulative per-run usage checkpoints contribute observed running spend -before the final ledger charge. Admission counts each unfinished operation's -maximum of its reservation and observed spend; hard stops count actual ledger -and observed running spend, rather than reservation estimates. Final settlement -atomically replaces the observation with the ledger charge. Incomplete or unknown -accounting remains conservative. Mid-run enforcement depends on provider usage -reports or existing supported pricing; providers reporting only at completion -cannot offer exact mid-call spend detection. - -Legacy execution records a renewable controller lease when claiming a queued run, -before provisioning. A live lease protects the run during overlapping service -deployments. An expired controller loses dispatch authority; a recovery worker -must establish that the previous execution stopped before starting a successor. - -## 11.7 Durable agent session goals - -Runner Protocol v2 negotiates a required `sessionGoals` capability and typed -`session.goal.*` commands and events. PRP v1 sessions remain supported and are -goal unsupported. The Codex app-server driver maps controls to -`thread/goal/get`, `thread/goal/set`, and `thread/goal/clear`; it observes -provider-created goal notifications and reconciles with an authoritative get -after each turn. An active goal suppresses premature run terminalization while -autonomous turns continue. The Paperclip runner's persistent ACP backend opts -in through the `_session/goal` extension and advertises its exact action subset. -Its pinned Codex/Claude executables retain the runner's Linux x64 qualification -requirement. Direct `codex_local` and `claude_local` adapters currently have no -live goal controller and remain unsupported, even when their underlying ACP -package exposes goals. Goal actions never change an agent's adapter, model, -permission policy, or rollout settings to manufacture support. CLI, one-shot -ACP, and providers without the structured extension remain unsupported. - -When a goal heartbeat settles, the runner suspends its durable authority even -under a warm lifecycle policy. Paused, blocked, completed, and rollover goals -must survive controller restart without relying on an in-memory warm owner. -The next run resumes the same provider session through the existing verified -checkpoint and authority-rotation path. - -The board composer treats `/goal` as an action command rather than Markdown or -comment text. It is capability-aware, and the issue thread renders durable goal -status and controls immediately above the composer. Goal completion enters the -normal run-result/completion arbitration path and does not directly close the -issue. - -## 12. Governance and Approval Flows - -## 12.1 Hiring - -1. Agent or board creates `approval(type=hire_agent, status=pending, payload=agent draft)`. -2. Board approves or rejects. -3. On approval, server creates agent row and initial API key (optional). -4. Decision is logged in `activity_log`. - -Board can bypass request flow and create agents directly via UI; direct create is still logged as a governance action. - -## 12.2 CEO Strategy Approval - -1. CEO posts strategy proposal as `approval(type=approve_ceo_strategy)`. -2. Board reviews payload (plan text, initial structure, high-level tasks). -3. Approval unlocks execution state for CEO-created delegated work. - -Before first strategy approval, CEO may only draft tasks, not transition them to active execution states. - -## 12.3 Board Override - -Board can at any time: - -- pause/resume/terminate any agent -- reassign or cancel any task -- edit budgets and limits -- approve/reject/cancel pending approvals - -## 12.4 Connection Tool Reviews - -Ask-first connection calls use a server-owned tool-action confirmation linked to -the authoritative action request. The task feed retains a stable record; dismissal -only hides the pending card above the composer. The ordinary composer remains -available while the card is open. Task and Connections decisions share one -transaction. Approval runs stored, signed arguments once; decline runs nothing. -The human decision remains distinct from provider execution success or failure. - -Always allow remembers the same agent, connection, and action, restricted to the -current project when present, with future argument values permitted. Explicit -denials, revoked access, catalog-definition changes, and formal approval gates -remain effective. A durable continuation receipt resumes eligible task context -with the recorded outcome after the agent yields. Uncertain interrupted execution -is surfaced without automatic replay. See [Task reviews](connections/TASK-REVIEWS.md) -for contracts, recovery behavior, Storybook, and acceptance workflows. - -## 13. Cost and Budget System - -## 13.1 Budget Layers - -- company monthly budget -- agent monthly budget -- optional project budget (if configured) - -## 13.2 Enforcement Rules - -- soft alert default threshold: 80% -- at 100%, the asynchronous evaluator triggers: - - set agent status to `paused` - - block new checkout/invocation for that agent - - emit high-priority activity event - -Board may override by raising budget or explicitly resuming agent. - -Native runs retain final usage receipts during a bounded accounting-only drain -after a governed wait cancels provider work. This does not accept late messages, -tool calls, or new completion proposals. Missing or incomplete receipts continue -to block budget admission. -After native recovery reaches terminal failure, an incomplete receipt holds new -work as recovering accounting; it does not open a hard-stop incident. When the -run no longer records a provider process (recovery clears it only after a -verified stop), no producer can deliver the receipt. The same holds after the -run's result was accepted. A remote run reaches the -same point when every sandbox allocation it used carries a provider -termination receipt (stopped or destroyed); a sandbox that may still run keeps -the debt pending. Accounting then records -its known part as an unpriced charge, so the unpriced-usage policy governs -admission. A later receipt still posts through late-receipt reconciliation. -When a lost receipt's observed usage has a known model and token counts, the -rate card prices it as an estimate whose provenance marks the usage as a lower -bound. A charge settled from a lost receipt never trips the blocking -unpriced-usage policy by itself, and neither does an unpriced charge with a -known partial cost or with no observed tokens on metered billing. A task's own -continuation and finalization are never held by that task's pending -accounting. -A controller that detaches for server restart loses checkpoint and completion -write authority. A closing event stream is not proof that the governed run -finished; the replacement controller must adopt and settle the original run. - -Complete direct Anthropic API receipts for `claude-sonnet-5` can use a versioned -list-price estimate when the provider supplies no run price. The receipt records -the rates and assumptions. Aggregate cache writes use the one-hour rate because -their TTL is unknown. Estimates are not invoices. Other models, billers, unknown -billing types, and incomplete receipts remain unpriced. - -## 13.3 Cost Event Ingestion - -`POST /companies/:companyId/cost-events` body: - -```json -{ - "agentId": "uuid", - "issueId": "uuid", - "provider": "openai", - "model": "gpt-5", - "inputTokens": 1234, - "outputTokens": 567, - "costCents": 89, - "occurredAt": "2026-02-17T20:25:00Z", - "billingCode": "optional" -} -``` - -Validation: - -- non-negative token counts -- `costCents >= 0` -- company ownership checks for all linked entities - -## 13.4 Rollups - -Read-time aggregate queries are acceptable for V1. -Materialized rollups can be added later if query latency exceeds targets. - -The Costs overview distinguishes unknown billing categories, missing model/price -data, and terminal runs awaiting final receipts. Bounded, company-scoped details -load on demand at `GET /companies/:companyId/costs/data-quality`. Entries link to -the run and, where applicable, agent configuration for future usage. Normal -credits and fixed billing are not unknown billing warnings. Unavailable historic -prices require no cleanup and must not be presented as zero-cost receipts. - -`POST /companies/:companyId/costs/retry-accounting` accepts a run ID, requires a -board cost reader with a non-viewer membership, and uses the existing audited, -idempotent accounting service. It never executes an agent. The UI offers retry -only when the saved run is eligible to record costs. Missing final usage changes -its display label after fifteen minutes. Elapsed time never discards a late -receipt. It settles the debt only for a native run whose recovery has ended -(terminal failure or an accepted result) and whose closure evidence has not -arrived after ten minutes (the remote runner reconnect grace plus a margin): the known usage is recorded as unpriced with -provenance saying closure evidence never arrived, and an activity entry names -the run. Like any charge accounting settled without its receipt, it never -hard-stops or pauses a company under the blocking policy, whatever its token -count; its activity entry and provenance remain the notice. A verified local -process stop closes the receipt source for a failed, cancelled, timed-out or -interrupted run alike, so a restart-cancelled run settles at once. A lost -receipt that already carries a provider-reported cost settles at that cost as -an estimate marked as a lower bound, not as unpriced. A failed native run that -never started provider work (no usage receipt, no replaced receipt source, and a -local process that never launched or was verified stopped) settles at once -with no charge and never holds admission; the empty receipt-source fence map -that every usage recorder binds is not evidence that provider work began. A native -turn that the restored Runner reports as lost on restore can never report its -usage. It does not hold the run's receipt open: the receipt closes when the -run's other turns do, and its provenance says the observed usage is a lower -bound. The same applies to a turn that was open when the provider session was -restored after process loss and that the restored Runner then reports -terminal without a complete usage report: the provider process that would have -sent the final report was stopped. Local and remote Runners both apply it; the -remote producer journals the restore as a receipt so a replacement controller -rebuilds the same turn state. Raw provider errors and credentials are excluded from -cost details. ACP usage retains the runtime-selected model, including defaults; -conflicting before/after model IDs do not authorize a single-model estimate. -Remote OpenCode billing uses only credentials forwarded to the execution target. -Cost detail reads use company-scoped partial indexes matching their filters and -sort order. Provider and biller cards retain estimate and missing-price labels; -an accounting retry confirmation survives removal of the resolved cost gap. - -## 14. UI Requirements (Board App) - -V1 UI routes: - -- `/` dashboard -- `/companies` company list/create -- `/companies/:id/org` org chart and agent status -- `/companies/:id/tasks` task list/kanban -- `/companies/:id/agents/:agentId` agent detail -- `/companies/:id/costs` cost and budget dashboard -- `/companies/:id/approvals` pending/history approvals -- `/companies/:id/activity` audit/event stream - -Required UX behaviors: - -- global company selector -- quick actions: pause/resume agent, create task, approve/reject request -- conflict toasts on atomic checkout failure -- no silent background failures; every failed run visible in UI - -Cold opens of ordinary tasks show the instruction and recent conversation before -related metadata and historical run activity. Earlier messages and diagnostics -load through **More messages**. Messages observed while the task stays open remain -visible; deep links still load their target. Compact reads retain the full reads' -authorization and redaction rules. Agent Chat and the classic task interface keep -their existing history behavior. - -Regular task chats and Agent Chat keep requests as compact, reopenable entries -at their original position in the feed. Dismissing any composer request clears -its form and pending reminder, including completion reviews, confirmations, -checkbox choices, suggested tasks, item verdicts, and runtime requests. Saved -interactions persist `dismissedAt` and `dismissedByUserId` through a board-only, -company-scoped endpoint with task-access, resolver-audience, and current review-policy -checks and an activity record. Review policy is checked under the task-row lock. -Dismissal never creates an answer, decision, execution, or continuation wake. -Raw runtime requests retain browser-local dismissal; reopening restores the -original request and draft. A failed save leaves the request open with an error. - -Sending a separate message closes the current composer request immediately, -before the send finishes. After a successful send, earlier requests stay in the -feed and can be reopened manually; a newer request can still open normally. -If sending fails, restore the previous form unless the person has opened a -different request. This presentation change does not answer or approve a request. - -A saved ordinary question becomes historical after a newer human task message -or explicit human dismissal. It remains answerable after completion without -reopening work or waking the agent. Dismissed ordinary requests, including native -completion reviews, no longer gate task completion; completion expires their -non-question cards through the existing lifecycle. Cancellation still expires -all pending requests. Agents must not repeat reminders for dismissed requests. -Tool actions, secret proposals, connection authorization, company-enforced -resolver restrictions, document/plan approvals, and separately configured review -stages retain their authority gates. Dismissal does not authorize their actions; -agents continue independent work without repeating the deferred request. - -## 15. Operational Requirements - -## 15.1 Environment - -- Node 24.11+ -- `DATABASE_URL` optional -- if unset, auto-use embedded PostgreSQL under `~/.paperclip/instances/default/db` - -## 15.2 Migrations - -- Drizzle migrations are source of truth -- local/dev startup applies pending migrations automatically where supported -- `pnpm db:migrate` applies pending migrations manually -- no destructive migration in-place for V1 upgrade path - -## 15.3 Logging and Audit - -- structured logs (JSON in production) -- request ID per API call -- every mutation writes `activity_log` - -## 15.4 Reliability Targets - -- API p95 latency under 250 ms for standard CRUD at 1k tasks/company -- heartbeat invoke acknowledgement under 2 s for process adapter -- no lost approval decisions (transactional writes) - -## 16. Security Requirements - -- store only hashed agent API keys -- redact secrets in logs (`adapter_config`, auth headers, env vars) -- forward authorized semantic tool arguments unchanged, including credential-bearing document and instruction content; the provider harness owns credential-content policy, and diagnostic redaction must not act as a save or execution gate -- CSRF protection for board session endpoints -- rate limit auth and key-management endpoints -- strict company boundary checks on every entity fetch/mutation -- restricted `skill_test` and `task_bridge` keys cannot enumerate company-wide run telemetry, workspace-operation logs, or the company secret catalog -- HTTP adapters use DNS-pinned outbound requests, reject redirects and link-local/metadata targets, and require an exact server-owner origin allowlist for private destinations -- external instruction bundle roots and exports that read them require instance-admin access; managed company-scoped bundles remain available through normal company authorization -- agent-authenticated callers cannot persist host-executed workspace commands, and restricted keys cannot invoke preconfigured workspace runtime controls - -## 17. Testing Strategy - -## 17.1 Unit Tests - -- state transition guards (agent, issue, approval) -- budget enforcement rules -- adapter invocation/cancel semantics - -## 17.2 Integration Tests - -- atomic checkout conflict behavior -- approval-to-agent creation flow -- cost ingestion and rollup correctness -- pause while run is active (graceful cancel then force kill) - -## 17.3 End-to-End Tests - -- board creates company -> hires CEO -> approves strategy -> CEO receives work -- agent reports cost -> budget threshold reached -> auto-pause occurs -- task delegation across teams with request depth increment - -## 17.4 Regression Suite Minimum - -A release candidate is blocked unless these pass: - -1. auth boundary tests -2. checkout race test -3. hard budget stop test -4. agent pause/resume test -5. dashboard summary consistency test - -## 18. Delivery Plan - -Current implementation note: the milestones below describe the original V1 sequencing. Several systems originally framed as future work have since shipped or advanced materially, including issue documents/interactions, blockers, routines, execution workspaces, import/export portability, authenticated deployment modes, multi-user basics, and the local/self-hosted plugin runtime. - -## Milestone 1: Company Core and Auth - -- add `companies` and company scoping to existing entities -- add board session auth and agent API keys -- migrate existing API routes to company-aware paths - -## Milestone 2: Task and Governance Semantics - -- implement atomic checkout endpoint -- implement issue comments and lifecycle guards -- implement approvals table and hire/strategy workflows - -## Milestone 3: Heartbeat and Adapter Runtime - -- implement adapter interface -- ship `process` adapter with cancel semantics -- ship `http` adapter with timeout/error handling -- persist heartbeat runs and statuses - -## Milestone 4: Cost and Budget Controls - -- implement cost events ingestion -- implement monthly rollups and dashboards -- enforce hard limit auto-pause - -## Milestone 5: Board UI Completion - -- add company selector and org chart view -- add approvals and cost pages - -## Milestone 6: Hardening and Release - -- full integration/e2e suite -- seed/demo company templates for local testing -- release checklist and docs update - -## 19. Acceptance Criteria (Release Gate) - -V1 is complete only when all criteria are true: - -1. A board user can create multiple companies and switch between them. -2. A company can run at least one active heartbeat-enabled agent. -3. Task checkout is conflict-safe with `409` on concurrent claims. -4. Agents can update tasks/comments and report costs with API keys only. -5. Board can approve/reject hire and CEO strategy requests in UI. -6. Budget hard limit auto-pauses an agent and prevents new invocations. -7. Dashboard shows accurate counts/spend from live DB data. -8. Every mutation is auditable in activity log. -9. App runs with embedded PostgreSQL by default and with external Postgres via `DATABASE_URL`. - -## 20. Post-V1 Backlog (Explicitly Deferred) - -- cloud-grade plugin marketplace/distribution -- richer workflow-state customization per team -- milestones/labels/dependency graph depth beyond V1 minimum -- realtime transport optimization (SSE/WebSockets) -- public template marketplace integration (ClipHub) - -## 21. Company Portability Package (V1 Addendum) - -V1 supports company import/export using a portable package contract: - -- markdown-first package rooted at `COMPANY.md` -- implicit folder discovery by convention -- `.paperclip.yaml` sidecar for Paperclip-specific fidelity -- canonical base package is vendor-neutral and aligned with `docs/companies/companies-spec.md` -- common conventions: - - `agents//AGENTS.md` - - `teams//TEAM.md` - - `projects//PROJECT.md` - - `projects//tasks//TASK.md` - - `tasks//TASK.md` - - `skills//SKILL.md` - -Export/import behavior in V1: - -- export emits a clean vendor-neutral markdown package plus `.paperclip.yaml` -- projects and starter tasks are opt-in export content rather than default package content -- recurring `TASK.md` entries use `recurring: true` in the base package and Paperclip routine fidelity in `.paperclip.yaml` -- Paperclip imports recurring task packages as routines instead of downgrading them to one-time issues -- export strips environment-specific paths (`cwd`, local instruction file paths, inline prompt duplication) while preserving portable project repo/workspace metadata such as `repoUrl`, refs, and workspace-policy references keyed in `.paperclip.yaml` -- export never includes secret values; env inputs are reported as portable declarations instead -- export preserves explicit company skill policy and retained legacy skill grants in `.paperclip.yaml`; absence of policy remains the open default -- import supports target modes: - - create a new company - - import into an existing company -- import recreates exported project workspaces and remaps portable workspace keys back to target-local workspace ids -- import forces imported agent timer heartbeats off so packages never start scheduled runs implicitly -- import supports collision strategies: `rename`, `skip`, `replace` -- import supports preview (dry-run) before apply -- import preview reports skill-policy and legacy-grant mappings before apply and rejects unknown policy schema versions -- GitHub imports warn on unpinned refs instead of blocking - -## Agent visual identity - -Agent appearances are stable, versioned ClipLab end-cap personas, separate from -behavioral instructions. Compact surfaces use on-demand cached PNG URLs; larger -placements may use a lazy live character. See [agent-personas.md](agent-personas.md) -for persistence, migration, rendering, and integration contracts. -### Task-backed agent chat (2026-09-10) - -Ordinary task and routine runs in standard mode may publish with -`send_chat_message({body,idempotencyKey})` or `POST /api/agents/me/chat/messages`. -The authenticated company, agent and run responsible human determine the sole -destination. Recheck execution ownership, membership, task/destination access -and canonical source privacy at commit. Reject missing recipients, restricted -source tasks, Ask/planning/review/watchdog runs and stopped runs. The shared -comment-write boundary enforces the fixed conversation agent. -Skill-test and task-bridge credentials cannot publish into standing chats. -Persisted skill-test run boundaries remain in force after a task-mode change. -Preserve the credential and external-user access intersection and recheck key -revocation, scope and responsible-person identity before commit. -Each new delivery charges the shared per-run cross-issue influence cap in the -same transaction. Retries, conflicting content and receipt-only recovery do not -spend another slot. Cap exhaustion returns the canonical 429 response and leaves -no message or newly created conversation behind. - -The chat messaging service atomically creates a waiting conversation if needed -and commits an attributed issue comment and audit record. Agent request IDs are -unique per conversation and author; keys incorporate the source task for retries -across runs, and changed content conflicts. Server-authored provenance identifies -the source task and run. Existing events refresh discovery, messages and unread -projections. Source execution transcripts remain outside the chat's linked-run -history. There is no delivery worker, message outbox or extra model turn. - -Sending does not change task disposition, cancel/settle a chat turn or suppress -automatic handoff completion notifications. `/new` permits future background -messages in the current history. Ordinary chat replies retain session checks. -Chat list/detail responses use per-user read state before human participation; -the foreground chat marks rendered messages read through an optional comment -cutoff, retaining unseen concurrent arrivals. Existing navigation ordering is -preserved with accessible unread dots and an aggregate Chat indicator. -Agent Chat is available to every person; external channels, push/email and private-chat -redesign are outside this contract. - -`enableAgentChat` is a retired compatibility key. Agent Chat is always available for Cloud and self-hosted instances, including existing people and instances with stored or managed off values. Conversation containers remain issues, unique by `(company_id, conversation_agent_id, conversation_user_id)`. The authenticated board actor supplies ownership; local trusted mode uses `local-board`. Ordinary company task access applies. A conversation's agent assignment and identity are immutable through ordinary updates; terminal status mutations are rejected. - -`GET /api/companies/:companyId/chats/:agentRef` reads an existing conversation or null. `POST` atomically resolves its issue when adding a chat or on first send/upload. `GET /api/companies/:companyId/chats` lists only the current board user’s conversations in that company, subject to ordinary issue read access. The Chat navigation opens a searchable secondary sidebar with agent avatars and a picker for starting or reopening the same per-agent conversation. Existing issue comment, attachment, document, interaction, and run APIs apply thereafter. User chat comments require an idempotent UUID `clientRequestId`. Conversation delivery preserves comment order through the existing issue execution queue; the durable comment outbox repairs the commit-to-enqueue crash window. - -The Chat navigation entry reopens the last agent conversation visited by the current user in the current company. The browser keeps this recent order and any existing conversation ID locally; unavailable agents and removed conversations are skipped. An agent chat that has no issue yet can still reopen from the agent roster. The agent chooser remains the landing view when no saved chat is available. - -The server owns conversation state: `waiting` plus `in_review` denotes a healthy idle conversation, and `active` denotes an unanswered or executing turn. Successful replies settle a turn; they do not finish the issue. Idle containers are excluded from execution-work counts, ordinary task lists, timer work, and recovery invocations. Failed/unanswered turns retain normal handling. Child completion never wakes or completes the conversation. Search and direct task access preserve history. - -Explicit pending interactions in these conversations, including agent question forms, -remain in the canonical attention feed under the existing company, task-read, and -addressee checks. A conversation's idle review status or ordinary question text -alone does not create attention. Answered forms leave pending attention and remain -in decision history. - -Standalone `/new` is an ordered queue command with no model response. It advances a durable session generation and boundary comment, resets only this issue's provider context, and preserves the issue ID and history. Generation checks reject stale context writes and replies. Fresh replay excludes earlier messages and summaries. The shared transcript renders a session divider. - -Chat prompts retain agent instructions and tools while directing clarification and task creation. Substantial execution belongs to linked, assigned ordinary issues. Ask mode remains non-mutating. The retired feature flag cannot prevent turns, resets, or discovery. Ordinary authentication, company and conversation ownership, privacy, run authority, and execution admission checks still apply. - -### Agent chat project handoff (2026-09-11) - -Chat supports research and full plan drafting/revision in its existing plan document. On handoff, each ordinary assigned task receives the relevant plan in its own `plan` document, committed with task creation before execution is scheduled. The source plan remains in the conversation. Plan acceptance hands off execution; it never switches the conversation into implementation. - -Chat instructions require selecting a suitable project, reusing an existing one where appropriate. The project requirement is prompt-only; ordinary projectless tasks remain supported. New parent relationships beneath conversation tasks are rejected by task services, including direct API creation and reparenting. Existing children remain readable/editable and can be moved elsewhere. The Subtasks panel is unchanged. - -The `create_project` runtime tool uses the normal project API with durable idempotency. `list_projects` and `list_project_repositories` support selection. Multiple `repositoryIds` select authorized catalog entries; multiple HTTPS GitHub `repositoryUrls` register existing repositories absent from the catalog. IDs and URLs may be combined, but cannot accompany an explicit `workspace`. URLs do not create repositories on GitHub or grant credentials. Execution uses normal repository access rules. Repository IDs are revalidated against the authenticated run's responsible user and connection grants. Agents should consider proper available repositories, clarify material ambiguity, and use repository-free projects when appropriate for non-code work. - -Confirmed project creation appears as a durable card in the shared task transcript, including selected repository links. Tasks are linked inline. Failed creation never produces a success card. Tool evals cover planning/handoff, project/repository selection, retries, permission and mode denials, and ordinary delegation regressions using the production chat directive. - -### User continuation after execution recovery stops - -An authenticated user message, a validated undelivered native message queue, or an exact failed-run Retry can start a fresh -native or legacy conversation turn once the prior execution is confirmed stopped. Retain the source history and uncertain -action outcomes; do not replay tool calls or reset the failed incident's automatic -retry budget. Existing pause, approval, budget, ownership, and dependency gates -remain in effect. See `doc/execution-semantics.md` for admission and stop-proof -requirements. -The task recovery notice offers Retry for eligible failures and verified native -startup cancellations, with failed attempts explained inline. Preparing native -turns keep the Steer label. Steer and Interrupt immediately move the submitted -messages from the composer queue into the conversation while delivery proceeds. -Provider acknowledgement remains authoritative; failed delivery restores the -latest queue with an inline error. Neither action produces a toast. - -### Managed AI authentication - -AI credentials can be adopted into the existing Connections system. A typed -`runtimeConfig.aiConnection` selects the responsible user’s personal default, an -explicit shared grant. The existing human-audience and agent-access permissions -apply; AI credentials have no separate agent-delegation exception. Selection preserves -harness/model routing and fails closed without ambient credential fallback. -Legacy agents retain their authentication until validated adoption. See -[AI Connections](connections/AI-CONNECTIONS.md) for company isolation, compatible -methods, lifecycle, runtime enforcement, and migration details. - -AI connections support concurrent runs and account tests. Admission does not -take an account execution lock or wait for another runtime's credential cleanup. -Private runtime homes remain independent. Credential writeback still checks the -active grant and saved generation before an atomic update. - -Missing personal AI credentials detected before adapter dispatch also produce -the inline connection card. Every missing binding must belong to the same -compatible AI provider. The responsible user connects their own account and -explicitly adopts Connections; another user's onboarding key is never reused. -Acceptance resumes only the matching configuration-blocked task through durable -continuation delivery. Unrelated configuration gaps retain operator recovery. - -The selected AI connection supports an on-demand usage probe through the common -connection service, independent of legacy/native execution. The board usage -endpoint rechecks company membership and the credential's human audience before -reading its stored token. Report all returned allowance windows, model/feature -scope, reset times, exhaustion and overage observations; missing values remain -unknown and unsupported methods/provider failures are explicit. The account -detail's Check usage action triggers the probe. No automatic detection, routing, -budget enforcement, credential refresh or credit purchase follows from it. - -Provider login failures create a provider-specific Connections card on the task -when the run fails, before generic recovery retries. Reconnect preserves account -identity and permissions. Compatible legacy agents may explicitly adopt a -validated connection inline; late failures must not invalidate newer credentials. - -### External channel failure scope - -Publication, provider-reply, and form-open permission failures fail the affected -operation. An unclassified provider 403 does not establish that the whole -connection is unusable. Known destination failures quarantine only that resource; -provider rate limits retain their retry deadlines. Confirmed authentication -failures put the endpoint in attention and mark connection health failed without -changing the connection's enabled setting. Admission and background reconciliation -block sends, assigned channel tools, and background receiver restart until -credentials are repaired. Signed provider lifecycle events remain admissible so -installation revocation still takes effect during an authentication stop. -Explicit disable and disconnect retain their existing behavior. - -### Task-bound email - -AgentMail is a default connection and does not require the experimental chat -setting. It extends the conversation/task pipeline with explicit email publication. Each owned inbox/provider thread binds one task; -external email senders do not gain board authority. Incoming correspondence uses -the assigned agent's normal execution controls. Internal task activity never -implicitly sends email. New outgoing conversations create child tasks and durable -send intents before provider contact. The board directs email work through the -normal task conversation; rich email cards show the correspondence and delivery -outcomes without a separate email composer. See -[AgentMail connections](connections/AGENTMAIL.md) for setup, transports, recovery, -authorization, and the API/CLI contract. - -### Experimental iMessage Photon channel - -A Photon Cloud project can represent one agent through the existing -experimental channel subsystem. DMs and explicitly enabled groups create or -continue task-bound conversations. Linked sender identity is the default; -telephone numbers, email addresses, names, and group membership do not grant -Paperclip authority. Photos/files and ordinary questions/confirmations use the -existing attachment, interaction, continuation, and publication contracts. -Pause and Disconnect govern runtime behavior independently of the UI gate. -Local Mac access, unsolicited conversations, and SMS/RCS -fallback are excluded. Live qualification is required before release readiness. -Pro shared allocation supports DMs only, with sender enrollment in Photon and -separate identity linking in Paperclip. Shared channels reserve one project, not -a pool phone number; group admission and publication are disabled. Dedicated -allocation retains one selected number and individually enabled groups. - -iMessage task completion ends a turn, not its conversation. Subsequent messages -reopen the same task, including after restart; only explicit `/new` or `/close` -allows the next message to start another task. The open task receives committed -inbound comments live, with “Sent from iMessage” attribution on user bubbles. - -See [iMessage Photon](connections/IMESSAGE-PHOTON.md) for the implementation -contract, setup, recovery, boundaries, and qualification status. - -### Native task completion - -A durable question or approval handoff survives a missing provider shutdown -notification. Paperclip settles its yielded result once the provider terminal -or exact host-owned shutdown proves stopped execution and settled tool outcomes. -Until then it retains the saved handoff and explains the shutdown hold. Recovery -must not repeat completed work or release an overlapping execution. See -`doc/execution-semantics.md` for the bounded settlement and restart contract. - -For ordinary low-risk tasks, accept the current agent's structured `done` claim -subject to explicit workflow constraints. Missing independent evidence or a -`needs_review` label alone must not create a human approval. Require a concrete -reviewer decision for a new review request. Keep unfinished work with the agent, -with bounded continuation and visible recovery. Preserve explicit approvals, -current task ownership, cancellation, dependencies, and newer task state. See -`doc/architecture/native-status-arbitration.md` for finish feedback and the -provenance-checked cleanup of historical automatic completion reviews. - -### In-app announcements - -A versioned remote JSON manifest supplies one optional board announcement. -The instance validates/caches content, proxies its raster image, and stores -user-scoped dismissals. Closing or following an action dismisses the ID; copy -edits retain it. Writes are board-only, idempotent and transactionally audited -using an authorized company's context. Viewers may dismiss their own card. -This is an explicit exception to company-scoped business entities: the -preference follows one account across companies on the instance. A separate -instance-level registry retains validated publication IDs, allowing offline -dismissal retries after withdrawal while rejecting caller-invented IDs. It -stores no announcement content, account data or interaction events. -See [Announcements](ANNOUNCEMENTS.md) for API and publishing details. - -### Keyboard shortcuts - -Keyboard shortcuts are always enabled for every signed-in user. There is no -instance setting and no personal preference that turns them off. - -### Persistent managed agent files (2026-09-28) - -The Instructions Editor and agent execution share one current agent-owned -directory, scoped by company and agent. The configured instruction entry is one -file in this directory. Registered private copies synchronize supported files -across tasks and sessions, separately from task workspace persistence. Saves -require verified provider stop and current authorization, then synchronize changed -files using per-file last-sync-wins; -new content is not stored as revision history. Existing deployed revisions and -saved execution formats remain compatible during adoption. See -[Persistent agent files](agent-files.md). - -Persistent-file storage limits are advisory for execution: a full folder cannot -pause the agent, fail its run, or prevent later runs. Show a warning on each run -while storage remains full, restore existing files so the agent can remove them, -and enforce the limits on saves. Cleanup clears the warning for future runs. - -### Unsafe native workspace exports - -An unsafe workspace link does not fail an accepted native task result. Retry -export automatically with confined entries only and keep archive confinement in -place. If the export remains unsafe, omit it and finish the saved result under -normal completion rules. Record diagnostics only in run logs; do not add a task -warning or manual repair action. This also applies to historical unsafe failures: -omit the already-rejected export, clear stale repair notices, and finalize the -accepted result without another provider turn, even when its old sandbox is -unavailable. Preserve current ownership and newer-work fences. See -`native-workspace-finalization-recovery.md`. - -## GitHub-synced skill sources - -- Discovery supports opt-in `Accept: application/x-ndjson` on the existing discovery - endpoint. Progress events report connection/download/tree/package stages, checked skill - counts, and the current package's file counts; candidate events contain metadata, - never file contents. Only the final `complete` event makes the scan selectable. - Interrupted streams discard partial results, and cancellation stops further - provider reads. The default JSON API remains compatible. The import dialog uses - reduced-motion-aware animations and shows indeterminate progress while saving - complete packages, without inventing completion percentages. - -- Sources and entries are company-scoped. Discovery and connection changes authorize - the current caller. Automatic and manual source refresh use the stored setup person's - connection and grant, revalidating their access and refreshing credentials on each - check. Saved connection IDs do not independently grant library visibility. Agent - setup derives the responsible person from the authenticated run identity. -- The import picker searches the deduplicated union of repositories from all connections - the caller can access, automatically choosing an authorized connection. Adding accounts - uses the standard GitHub setup in Apps and retains the import draft. Repository URLs - use the default branch; `/tree/` URLs select a branch, including slash-containing - names, without a separate branch or credential selector. Pasted URLs also prefer - the caller's eligible GitHub connections, including for public repositories; - anonymous access is used when no authorized connection succeeds. An explicitly - saved connection must remain authorized. Repository identity still comes from - the GitHub API; quota errors are distinct from access denials. -- Discover every `SKILL.md`, including hidden/deep directories. Resolve the tracking - ref to an immutable commit once per operation through a shallow Git fetch. Read - the complete local Git tree and blobs without checkout, archive transformations, - or per-file API calls. Git's receiving/preparing percentages feed download progress. - Git must be installed on the server. Downloads are bounded to 128 MiB and three - minutes, with two simultaneous downloads and at most four cached/downloading - snapshots. A ten-minute in-memory index of temporary repositories, scoped by - company, caller, run, and grant, reuses a pinned snapshot for preview/import; - authorization is checked before every reuse. No credentials are written to disk - or inherited from host Git configuration. Cancellation terminates the Git process - group and removes incomplete downloads. Symlinks and submodules are reported, - never traversed; nested skill roots are independent package boundaries. -- Select packages as units, with included-file trees and bounded read-only previews. - Nested packages have independent checkboxes; supporting files have no individual - selection. Persist file manifests, explicit missing/out-of-package reference findings, - and declared `compatibility` requirements with source entries. Preview reads reauthorize - the current caller and audit the requested package at the scanned immutable commit; - discovery and source metadata contain no file contents. Reference checks are advisory, - do not establish complete runtime dependencies, and never expand package boundaries automatically. -- New UI imports use `packageMode: repository` to preserve supported repository files - in one company-scoped immutable snapshot. Selected entrypoints become library skills; - other skills remain supporting files. The root `paperclip.skills.json` may declare - version 1, validated `skills` paths and `requirements` text. Invalid manifests fall - back to discovery. The dialog shows every skill and containing folder, selects all - discovered skills, and collapses package files. There is no import-mode chooser; - existing directory-mode sources and API callers remain compatible. - Unsupported links, oversized files and files rejected for unsafe execution or - secret disclosure are omitted with persistent warnings. Malformed entrypoints - stay selected with Needs setup warnings; usable selected skills still publish. - Limits are 9,990 repository entries, 1 MiB per file and 61 MiB total. The dialog - exposes included files and setup requirements without repair choices or another - confirmation. Installed file views read saved repository bytes and resolve links - from each instruction file's original directory. -- Each repository-backed version references its snapshot and original entrypoint path. - Runtime materialization adds a discovery wrapper and preserves the original tree under - `.paperclip-repository/`, including binaries, modes and credits. Original instructions - are unchanged. `agents/openai.yaml` discovery policy is retained beside the wrapper. - Existing adapters carry self-contained copies, so multiple entrypoints may duplicate - runtime bytes. Shared-file changes version all selected skills; pinned runs remain - on their snapshot. Importing does not install dependencies or configure host tools. -- Stage and audit complete packages before publishing. Scripts are allowed through - the existing content audit and never run on import. Persist binary bytes and executable - flags in immutable package snapshots (legacy entries default to UTF-8/non-executable). - Compatible readers support inline and external inventories. PostgreSQL retains - receipts, file mappings and exact Git restore recipes; the storage backend retains - bytes. No package is persisted without a valid selected skill. Legacy conversion - is automatic and bounded. Unused Git caches can expire and reacquire the recorded - commit after current authorization and hash checks; authored/unproven legacy bytes - remain durable. See [skill package storage](skill-package-storage.md). -- Paperclip ownership is `managed`, with a null source locator and an immutable - current version. Content mutations prepare durable bytes before a short, - authorization-checked compare-and-swap publication. Acknowledged changes do not - depend on an installed web-server directory. Repository installations use the - same committed-version read and execution boundary; linked-local sources are - explicitly outside the empty-disk guarantee. Execution selections, including - empty selections, persist before delivery; recovery reconstructs exact admitted - digests and active/recovering pins prevent deletion. See - [conversion and compatible upgrades](skill-ephemeral-web-servers.md). -- Refresh is serialized per source, checks saved selection revisions, and publishes - valid skills plus per-entry outcomes with required activity records in one transaction. - Failed packages retain prior versions; access/scan/download failures retain all content. - Record attempted and successful refresh times separately. -- Match by repository identity and exact path. Preserve IDs, keys, assignments, folders, - and history. New key collisions cannot overwrite unrelated skills. Only changed package - bytes or executable modes create versions. Library reads, tests, and subsequent unpinned - runs use immutable installed snapshots; pinned/active runs retain their versions. -- Runtime version directories are disposable caches. An hourly, bounded sweep may - evict non-current copies unused for one hour, preserving active and unresolved - execution pins. Selection, materialization, and pin persistence serialize with - eviction across processes. Missing copies rebuild from immutable stored packages - or legacy database bytes without upstream access. Warm preparation uses manifests - and file identities, avoids repository-content SQL/object reads, and does not - reconcile unrelated library entries for known selected repository skills. See [recoverable disk cleanup](recoverable-disk-cleanup.md) - for rollout controls, legacy grace periods, and interrupted cleanup recovery. -- Manually selected sources retain reviewed selection, with new candidates checked by default and - explicit folder exclusions retained. Upstream deletion keeps installed content with - removed status; moves are removal plus discovery. Deselect retains content - and assignments. Originals are read-only, with the existing independent fork workflow. -- Source disconnection requires a saved revision and an explicit copy/delete choice. - Remove the source and all entries to allow a fresh repository import, including - legacy disabled sources. Copy materializes complete local files in the caller's - My Skills folder, retaining skill IDs, keys, versions, and agent assignments. - Delete applies skills.remove to every linked skill (including deselected entries), - rejects skills still used by agents, and removes skills and source atomically with - the activity record. Copy applies skills.edit and skills.create to every linked - skill; failed writes or audit persistence leave the source and imported skills intact. - -- Adopt recognizable existing GitHub imports without provider calls or content changes; - resolve missing repository IDs and full snapshots on successful refresh. Exclude bundled, - catalog, local/project, skills.sh, and unsupported hosts. Existing GitHub import and update - endpoints delegate to sources while retaining response shapes. -- GitHub.com project repository attachments enqueue durable skill-source import jobs in - the project mutation transaction. Skill import completes asynchronously and cannot - fail project creation. Configured refs use the existing project workspace precedence; - absent refs track the default branch. Sources without skills remain tracked. -- Enabled sources check upstream every five minutes; unchanged commits avoid downloads. - Manual Refresh uses the same serialized publication pipeline. Automatic updates may - be paused independently of selection. Project sources select future additions unless - excluded; existing manually imported sources retain their selections. Legacy projects - adopt through an explicit company-scoped, project-authorized endpoint, never a GET. -- Repository size fallback first retains skill folders and transitively referenced - repository files within the existing file/byte limits, preserving original paths. - If that set is still too large, bounded skill folders remain importable. Missing - references are advisory and do not invalidate otherwise valid selected skills. - Web routes and inline Markdown examples are not package dependencies. Preserve - installed versions if fallback would omit their previously installed shared files. - Project sources retry complete packaging; selections and exclusions remain unchanged. - Refresh reinspects cached dependency-rejection errors once even at an unchanged - upstream commit. Content validation and company/project access remain enforced. -- Unattended refresh stores setup identity and connection/grant identifiers, never token - values. Current membership, connection authorization and skill policy are revalidated. - Unrecoverable legacy setup identities require explicit connection repair. Retries are - bounded and backed off; leases survive restarts and shutdown cancels owned downloads. -- Project-derived skill visibility is the union of current originating project audiences. - Core uses project authorization, including agent/responsible-user intersection, for - discovery, reads, mutations, files, versions, tests and runtime delivery. Unauthorized - IDs return not found. Company imports retain their existing visibility. Forks preserve - project provenance. Unsupported public publication or portable export is rejected. -- Detachment retains installed content and project access provenance while stopping that - project's tracking. Ref replacement never rewrites existing references. An orphaned - project source remains restricted after its last originating project is deleted. -- Executions resolve unpinned references from local immutable versions, persist the - resolved inventory, and reuse it on resume after rechecking access. Explicit unavailable - references fail actionably rather than silently omitting instructions. Background - GitHub checks never delay execution. Existing version pins remain supported. -- Upstream editing, push webhooks, commit/PR creation and advanced reference pin UI - remain outside this release. - -## Public assistant connection (opt-in) - -The user-authorized MCP surface connects assistants to an explicitly selected -company as the consenting person. It exposes first-party task reads, additive -task creation and comments, durable documents and approval links. It reuses -existing domain authorization and scheduling; OAuth does not grant agent -identity, native run ownership, approval decisions or third-party credentials. -See [Public MCP](public-mcp.md) for the implemented instance-side boundary, -configuration, plugin packages and outstanding hosted release gates. The -[delivery plan](plans/2026-09-30-paperclip-public-mcp-and-plugins.md) separates -external agent participation and granted third-party tools into later releases. - -The experimental OpenAI Dot Runner provider uses a separate `/mcp/runner` -agent OAuth resource. It reuses the public gateway's browser/device consent, -client metadata verification and signed event delivery, but requires one-use -agent pairing and normal run admission. Personal grants cannot authorize -Runner operations. See [OpenAI Dot Runner](openai-dot-runner.md) for the -self-hosted release boundary and remaining account qualification. - -### Experimental AI connection routing - -Opt-in plugin routers may represent a pool as an AI runtime binding. Core keeps -company and credential authorization, atomically records task/agent affinity and -a pool cursor, and persists concrete native recovery evidence. The full contract -is in [AI-CONNECTION-ROUTERS.md](connections/AI-CONNECTION-ROUTERS.md). Disabled -routing cannot allocate new tasks; already admitted native runs remain recoverable. - -### Connection instructions - -Connections can store optional, versioned agent instructions independently of -provider and transport. Catalog templates control editor visibility; saved -settings and runtime delivery also support connections without a template. -The server includes instructions only when the connection and at least one -action are available to the run's agent and responsible identity. An immutable -per-turn snapshot participates in session compatibility, so subsequent turns -remove stale instructions after edits or access revocation. See -[Connection instructions](connections/CONNECTION-INSTRUCTIONS.md) for contracts, -UI conventions, custom adapter integration, and initial memory templates. - -### Remote Codex model compatibility - -Fresh remote Codex Runner preparation can replace a model whose verified CLI -minimum exceeds the supported image CLI version. It selects a compatible older -model in the same class, then the stable Runner default, and saves that effective -model before checkpoint selection. A visible task warning and local run-log event -record the substitution. This does not change agent settings, rewrite admitted -executions, or bypass artifact, ownership, permission, and budget gates. See -[remote Codex compatibility](execution-semantics.md#remote-codex-model-compatibility). - -### Native provider capacity retry - -Committed, run-bound Codex `serverOverloaded` terminal failures display the model -capacity error directly and schedule at most two automatic retries, after one -and two minutes. Retries share the execution failure budget, retain task history, -and honor current ownership, review, governance, pause, dependency, budget, and -cleanup gates. Restart or duplicate finalization must not create another -successor. Permanent model/auth incompatibility and usage-limit exhaustion retain -their existing operator recovery requirements. - -## Internal agent commentary - -Audit → Commentary is a board-only page with agent/type filters, text search, -50-message pages and full-message dialogs. Filters, page offsets and selected -messages have shareable URLs. Read endpoints require company membership, -`commentary:view` and source-task visibility. All human company roles, including -viewers, receive this permission by default. An upgrade migration grants it to -existing active human members while preserving existing commentary grants. -Human invitation approval adds the commentary default when a saved grant list -omits it, including invitations created before the upgrade, and preserves explicit scopes. -The separate `audit:view_agent_actions` permission continues to govern agent-action -audit reads, detailed audit filters and CSV exports. - -`agent_commentary` stores company-scoped, attributed complaints and suggestions -as free-form text in the instance database. Legacy agents use the default -`complain` and `suggestion-box` runtime skills; native runs use dedicated tools -in standard, ask, and planning modes. Submission never changes task disposition -or routes feedback externally. See [Agent commentary](agent-commentary.md) for -authentication, replay, document-sized limits, inspection, and deletion semantics. - -## Company decision-model service - -Company Settings → General can configure one shared API-key connection for optional internal decisions. V1 supports OpenAI Decisions and Jev through OpenRouter. Companies start unconfigured; background sponsorship defaults on when configured, while an explicit off setting persists. Human and agent calls retain current responsible-user, connection audience, resource, and agent installation checks. Only explicitly registered internal background features can use company sponsorship. - -The internal service provides local-only availability and bounded, reauthorized execution. It records metadata-only invocation history and independent fractional service charges in the existing cost ledger, applying company and applicable agent/project budgets under the accounting lock. Unknown dispatch charges retain reservations until audited resolution. Settings/testing require connection management permission; history uses existing cost visibility. See [decision-models.md](decision-models.md) for the contract, supported models, accounting, privacy, and endpoints. - - -### Task workspace ownership (2026-10-09) - -### Filesystem access boundary (2026-10-20) - -Filesystem isolation belongs to the OS, execution environment, and storage layout. -Paperclip authorizes the current task/run, company, explicitly selected configuration, -execution policy, and repository credentials. It does not infer file permissions from -other tasks or agents that previously used the same directory, overlapping roots, -symlink aliases, or shared Git storage. Existing files under an authorized task root -may be read subject to OS access and API path containment. - -Private task records, comments, attachments, run logs, full workspace metadata, and -operation logs retain their app authorization. Workspace `_issuePrivacySources` and -operation privacy metadata protect app-record disclosure; they do not grant or deny -filesystem execution or task-bound file browsing. Current trust and sandbox policy, -publication receipts, binding checks, and lifecycle locks remain authoritative. - -A task's organizational project and filesystem workspace are independent. New tasks that need a filesystem and have no selected/configured workspace get a company/task directory, not the agent home. Plain tasks need neither Git nor a project. Existing bindings and admitted roots remain authoritative; changing project does not move files. Explicit workspace selections apply at a subsequent normal admission after prior execution and persistence obligations settle. - -The remote default also uses a task-owned empty source for an authorized project -with no configured workspaces or explicit workspace policy. Its derived managed -folder is not an admitted source. Existing bindings, explicit source selections, -and required Git or folder settings do not become empty-source fallbacks. - -Chat connection and destination settings may supply project/workspace defaults. Destination fields override connection fields; omitted values inherit and null clears the inherited choice. Resolve and authorize these defaults once for a new conversation, then retain the resulting task intent. Existing conversations do not change when settings change. Trust confinement still takes precedence. - -Agents inspect/select workspaces and request contained repository preparation through ordinary authorized APIs. Repository preparation is idempotent and may require the next admission; it never claims a host-only clone is available in an already-running remote session. Simultaneous tasks may still explicitly share one physical local/SSH folder. Remote sharing requires actual placement and coherent persistence capability. - -See [the layer architecture](plans/2026-10-09-task-workspaces.md) for resolution rules, module ownership, persistence versions, rollout, and verification. - -### Experimental native remote workspace authority (2026-10-30) - -The managed feature `remoteWorkspaceStorageEnabled` defaults on for Cloud and -self-hosted instances. New native Paperclip Runner sandbox workspaces use remote -authority automatically when durable storage is configured and their fresh -source is supported. Existing host workspaces and configured local folders keep -their legacy path until explicitly imported. An explicit stored `false` disables -new enrollment; managed configuration can override the stored choice. External -PostgreSQL records workspace identity, location, writer epoch, and the committed -saved version; S3 holds encrypted portable snapshots. A stable deployment -encryption key is required. The webserver uses disposable transfer/restore -scratch instead of a durable task checkout. New workspaces without these -prerequisites retain the legacy path. The local, SSH, and plugin environment -drivers, legacy adapters, and Dot retain their existing behavior. - -One writer owns a logical workspace. Preparation establishes a saved baseline; -finalization saves supported changes before settling persistence. A webserver -replacement reconnects to the existing allocation. A committed saved version can -restore elsewhere without the old sandbox or Git remote, including supported -dirty Git state and unpushed commits. Changes after the last save still depend -on the live allocation; this release provides no periodic backup of those edits. -Failed saving retains the source and recovery obligation before destructive -cleanup. File browsing distinguishes live bytes from a saved copy and its time. -After a final save, older failed copies can expire when all their save attempts -have stopped. Cleanup retains copies whose upload completion is unknown. This -includes operations from older builds without save-attempt records. A final -save alone does not prove that an older upload has stopped. - -V1 supports plain task folders, Git sources, and managed mutable repositories. -An authorized mentioned project's configured Git repositories become writable -copies inside the task workspace, under `.paperclip-repositories/`. The copies -retain their saved files, paths, refs and pins on subsequent turns. Preparation -rechecks the access and trust policy of each retained source project. This does -not grant permission to change a shared source folder or push to a repository. -Repositories attached on a later turn are acquired before they become part of -the next committed archive. Snapshot coverage is separate from the requested -source recipe. New copies honor `repoRef`, then `defaultRef`, then the remote's -default branch; an invalid ref cannot publish a partial secondary checkout. -It rejects explicitly read-only sources, -explicit concurrent shared writers, and custom cleanup/teardown commands on -remote close. Manual workspace runtime commands, jobs, and services are also -unsupported in V1 and must fail before local filesystem work. Agent tasks remain -the supported command path. Disabling new enrollment preserves existing authority -and recovery. An enrolled workspace whose persistence prerequisites are lost -must fail admission rather than fall back to a controller checkout. - -A reference to an authorized project with no configured workspace source does -not attach a folder. It adds a resolution warning and preserves the task's -workspace. A configured cross-project local folder must be imported before remote -acquisition. `PAPERCLIP_MULTI_PROJECT_WORKSPACE_SYNC_REMOTE` gates configured -sources; a source-less project reference needs no folder and stays metadata-only -when that switch is off. - -Task failure notices show the redacted workspace validation explanation when -preparation fails. Existing retry permissions and execution gates still apply. -This includes retained-folder access and trust denials. Low-trust project boundary -denials retain their separate project-access action. - -An imported saved Git workspace can resume from its saved files after its old -local repository is removed. Local repository paths remain invalid for new -remote acquisition. Saves reject unfinished Git operations, including rebase, -cherry-pick, revert, merge, and bisect. The source files remain protected. Finish -or abort the Git operation before saving. V1 does not claim a complete saved -copy while these continuation records cannot be restored safely. - -Saved-copy browsing reads the committed archive in place. It range-reads and -decrypts the archive header and manifest, and verifies the manifest against the -snapshot's recorded manifest digest. Listing and file details come from that -verified manifest. Reading or downloading one file range-reads and decrypts only -that file's bytes into a private temporary file, verifies them against the -file's manifest SHA-256, and only then serves them. No browse request downloads or -extracts the whole archive, so archive size does not limit saved Files access. -A preview decrypts and verifies the file in memory, within its preview size limit. -A download reserves the file's size against a 4 GiB per-process budget and checks -free temporary disk with a 256 MiB margin before writing. Cleanup releases the -reservation. A file larger than the budget reports `too_large`. A storage read -that sends no bytes for five minutes is closed. The process keeps parsed -manifests of recent snapshots, charged by manifest size plus their entries, up -to 256 MiB. Concurrent opens of one snapshot share one manifest read, and at most -two manifests are read and parsed at once per process. Manifests are limited to 64 MiB, the archive format's limit, and -500,000 entries; a larger saved copy reports `too_large`. -Saved files carry no modification times, so the recent view of a saved copy is -ordered by path. Availability batches share one verified access per execution -workspace within the request and allow at most eight remote workspace accesses. -Each path still checks current authority. -The changed-files view needs the repository status, so it still restores the -complete archive for that request. That extraction admits at most two open -extractions per process. It limits an encrypted archive to 512 MiB, its manifest -to 4 MiB, and extraction to 10,000 entries. It reserves at most 2 GiB of -temporary space across those accesses and checks free disk space with a 256 MiB -margin. A saved copy beyond those limits reports changed files as unavailable; -its other views stay available. Capacity failures stop before an object download. -Cleanup releases capacity after it removes the owned files. These limits apply to -saved-copy file access. Agent workspace saves and restores retain their existing -archive limits. -Each managed repository preparation also saves a complete workspace archive. -These saves preserve each preparation boundary; V1 does not combine them or use -incremental uploads. - -Snapshot retention removes all stored object versions and delete markers before -it records deletion. S3 credentials need `s3:ListBucketVersions` and -`s3:DeleteObjectVersion`, in addition to normal object access. S3-compatible -endpoints must support object-version listing. Permission errors -and object locks retain the cleanup obligation for retry. Other storage deletes -keep their existing behavior unless they explicitly request all versions. - -Ordinary sandbox Runner tasks retain the existing API upload restriction: -`call_api` cannot read a workspace file through `files[].path`. Use -`register_deliverable` to register the file, then pass its `artifactId` to -`call_api`. Plugin task providers keep their existing file-read capability. - -This feature does not remove the instance volume or qualify HTTP traffic drain, -active provider handoff, or other instance data paths. See -[setup/import](DEVELOPING.md#experimental-native-remote-workspace-storage) and the -[snapshot storage contract](DATABASE.md#experimental-remote-workspace-snapshots). - -## Company fast-response service - -Companies may configure an optional shared API connection and model for short acknowledgements of accepted human turns. Fast responses use direct SDK inference alongside normal agent work, with a three-second admission deadline, bounded prompts/output, current authorization and independent `fast_response` accounting. They are server-marked platform receipts attributed to the agent, never completion, interaction or recovery evidence. Company-sponsored unlinked external senders are explicit; linked-user permission denials are never bypassed. See [fast-responses.md](fast-responses.md) for provider, delivery, history and endpoint contracts. - -Project-derived source removal retains dynamic project access for kept editable -copies and forks. A removed source is absent from Sources and cannot refresh; -ordinary repository attachment does not restart it. Explicit project adoption can -restart tracking without widening the retained content’s access. - -Codex execution homes are scoped to the agent’s authorized skill inventory, -including its immutable source versions and an empty inventory. Revocation and -version changes select a separate home and session store while retaining the -selected model account’s credential source. Repository skill auto-update trusts -its upstream authors; operators can pause updates to review changes manually. - -### Task collection creation visibility (2026-10-13) - -The task Filters menu also offers **Routine tasks**. It selects tasks with -`originKind=routine_execution`, including manually triggered routine runs, even -when other automatic tasks are hidden. The routines list's **View all tasks** -link opens `/issues?view=all&originKind=routine_execution&sortField=created&sortDir=desc` with that checkbox -selected. The URL retains the selection across reloads and task navigation. -Sort controls save `sortField` and `sortDir` in the URL. URL sorting overrides -saved collection sorting, and the routines link opens newest-created tasks first. -Shared sort links retain their order across task interfaces. Workflow sorting -requests the existing authorized dependency-edge projection. Search and board -queries keep their existing server order. Blocked-attention lists reject -`sortField=created` with `422` because they use their own activity order. -Search and pagination apply the routine origin filter on the server. Creation-time -sorting also runs before pagination, with task ID as a stable tie-breaker. - -Tasks and project collections default to hiding agent/system-created tasks and -provide a saved **Show agent-created tasks** filter. Classify creation using -creator identity and origin; assignee/responsible users do not imply human -authorship. Routine executions remain automated even when manually triggered. -Creator-less ordinary tasks are system-created. All chat-channel tasks count as -bot-created for this filter, including human-initiated inbound conversations. -Human ordinary and onboarding tasks remain visible. Apply collection creation filters before search limits and -pagination, and retrieve hierarchy context separately under existing access checks. -Context does not count as independent results or board cards. Bounded context -reads retain loaded rows while users reveal more of the tree. - -Mine, unread counts, badges, and task toasts use the same personal participation -rules. Automatic creator attribution, synchronized provider comments, manual routine -runs, responsibility, and read-only activity do not admit automatic work. Human -assignment, direct board/API participation, and open addressed requests still qualify. -Only current requests on open tasks qualify. Historical questions remain answerable -without adding tasks to Mine. A request stays unread until the person reads or -replies; automatic task updates do not mark requests as read. -Existing archive behavior and access checks remain in force. Inbox discovery/Blocked preserve -participated automation and hide unrelated automation by default. Agent profile -history, routine history, and task-detail subtasks remain complete. API consumers -that omit the optional creation filter retain their existing list behavior. - -Project-derived editable forks retain the project audience present when created. -Later repository attachments do not grant access to those edits. Re-adopting a -removed source uses a separate active tracking identity; retained copies keep -their original provenance. Project membership and visibility remain dynamic. - -Automatic skill checks compare tree identity as well as commit identity, so -empty commits do not download or rescan content. Automatic scan work uses a -durable per-source 512 MiB allowance with 512 MiB/hour refill; installed versions -remain usable when it is temporarily limited, and authorized manual Refresh -remains available. Initial imports authorize skills.import before provider work. -At most 20 jobs per project and 100 per company enter the active initial queue; -additional repository bindings remain durable and are admitted automatically -when capacity frees. Project workspace create/update/delete use the existing -workspace-write authorization evaluator. Policy-denied initial jobs await explicit -adoption/repair rather than repeatedly scanning. - -Agent-created project sources may reuse only the GitHub connection and grant -already authorized in the persisted active run identity. The responsible person -is recorded for attribution; that identity alone does not permit searching other -personal connections. Without a selected run grant, Sources requires an explicit -human connection repair before background import or update. - -Automatic initial repository imports reserve durable company work credits before -provider reads: 512 MiB capacity, refilling 512 MiB per hour, with a 128 MiB -reservation per attempt. Failed downloads and package fallback keep the full -reservation. Successfully linked small imports refund unused measured work. -Completed bindings do not reset company credits; waiting jobs retry on refill. -The company_skill_import_budgets table stores no credentials or source contents. - -### Retired Pipelines experiment (2026-10-19) - -Pipelines, its review queue and learnings, stage automation, REST API, CLI commands, -and experimental setting are removed. Existing database tables and records remain -for data preservation; there is no migration that drops or rewrites them. The -independent Cases and Routines features remain available. - -Company-managed [Prompt Injection Prevention](prompt-injection-prevention.md) -optionally classifies existing tool-result boundaries through a shared OpenRouter -connection. Settings → Connections is permanent; valid model verdicts take -precedence and dependency failures use the original built-in detector. - - -### Personal workspace preference - -Tasks defaults to All tasks when the viewer has no saved task view. Explicit -view URLs and existing saved task views retain precedence. -Task tables default to Last updated descending without an active sort header; -an explicit column sort selects the header and shows its direction. - -Coordinator is the standard workspace for every person. It is not an experiment. -Settings → Experimental offers **Classic workspace**, a personal opt-in that -defaults off. Existing Coordinator experiment values, including historical off -values, do not opt anyone into Classic. The choice is saved per user across -companies on an instance in `user_workspace_preferences.classic_workspace_enabled`; -it is not an instance feature or a company capability. Turning Classic off returns -to Coordinator without modifying canonical tasks, agents, artifacts, permissions, -or runs. Local-trusted development uses the existing shared `local-board` identity, -explicitly identified in Settings. Anonymous and agent actors cannot read or update -this personal choice. - -`GET /api/workspace-preferences/me` reads only the caller's choice. For client -compatibility, `coordinatorWorkspaceEnabled` reports the inverse of the Classic -opt-in. The old stored Coordinator column is retained but no longer selects the -workspace. `PUT /api/companies/{companyId}/workspace-preferences/me` saves the -Classic choice through that same inverse property with authorized company audit -context; active viewers may update their own presentation. The preference and -sparse audit event commit atomically. Client caches are keyed by human identity, -and switching waits for successful persistence; unavailable preferences use the -standard Coordinator workspace. This preference does not replace -`enableClassicTaskInterface`. - -## Agent connection administration (2026-10-13) - -Standard agents can configure connections through the same domain APIs used by the app, bounded by their grants and their responsible user's current authority. Fresh standard agents for default Operators can manage app/tool connections, chat/email channels, AI accounts and pools, profiles and gateways without additional grants. Existing grants are not backfilled; explicit restrictions persist. Gateway administration retains the responsible human's administrative permission and does not grant agents blanket `tools:admin`. - -The agent performs authorized setup, reuses saved accounts and drafts, and hands off only required human authentication, consent, credential entry or identity confirmation. Saved handoffs resume through existing task continuation after current authority is rechecked. Management, tool execution and approval remain separate. Private accounts, host commands and stored credentials retain their boundaries; agent-issued gateway clients preserve issuer and responsible-user restrictions at use time. - -Authorized policy administration includes user-requested changes that loosen the acting agent’s own tool controls. These writes remain bounded by management grants, resource scope and the responsible user’s current authority; they do not need an additional approval solely because the agent is a target. Subsequent tool calls obey the resulting policy. Agents cannot approve their own governed action requests or edit their own principal permission grants. - -Ordinary tool approvals remain reviewable without an implicit one-hour deadline. Authenticated OAuth refresh preserves a pending approval for the same saved action and authorization. Sign-in repair retains an accepted decision and resumes through connection-change events after revalidation; changed consent, permissions, credentials or action targets require a new review. Current access, explicit deadlines, task closure and execution receipts remain enforced. See [Delayed tool approvals](execution-semantics.md#delayed-tool-approvals). - -The implementation, operation inventory, permission matrix and fixture qualification are recorded in [Agent API parity for connection configuration](plans/2026-10-13-connection-api-parity.md). Provider fixtures establish API behavior; they do not constitute live provider setup or an empirical agent-success claim. diff --git a/doc/SPEC.md b/doc/SPEC.md deleted file mode 100644 index e7c5c2b044b..00000000000 --- a/doc/SPEC.md +++ /dev/null @@ -1,903 +0,0 @@ -# Paperclip Specification - -Target specification for the Paperclip control plane. Living document — updated incrementally during spec interviews. - -Proactive in-app updates let an active standard task or routine publish to its -agent's persistent conversation with the run's responsible human. The server -derives the destination, enforces current authority and company-visible chat -privacy, and persists an attributed, idempotent comment with source-task provenance. -Unread state belongs to the viewing human. Sending is independent of task -completion and chat execution; future messages remain possible after `/new`. -The V1 contract is specified under experimental task-backed agent chat in -`doc/SPEC-implementation.md`. - -Experimental standalone cases provide persistent structured work records that can -exist independently of execution. Prompt-driven handoffs move their primary -ordinary task between agents while retaining case data and history. Existing task -permissions, execution ownership, budgets, and review gates remain authoritative; -the concrete contract is in `SPEC-implementation.md` and the persistent case -handoff implementation plan. - -A terminal handoff to `done` explicitly requests case closure on accepted -completion of its primary task. Existing completion and review gates remain -authoritative; an open case alone never creates an execution requirement. - -Native agents have dedicated case creation/read/update and handoff tools. -Native creation requires first-task kickoff and either `workflowId` or explicit `workflow_document_text`, -stored as the primary task's `workflow` Markdown document, with optional additional -initial documents. Recommend both a Mermaid `stateDiagram-v2` map and prose -defining every transition's conditions, evidence, destination and wait/retry rules; -the server does not parse graph labels as executable transition rules. -Documents belong to ordinary tasks; cases surface authorized document references -and history without copying bodies. Historical case document storage is retained. -Case -awareness is supplied by the harness; compatible session continuations carry -fresh routing information and changed facts, with full context retained for -new sessions and resume failure. - ---- - -Reusable workflows stay inside the Cases experiment. A workflow is a company-shared -name and Markdown prompt; a case may adopt, leave and revisit workflows without -changing its primary task. The task's workflow document is the effective copy, -so shared edits affect future adoptions and explicit refreshes. Existing handoffs -record workflow/phase movement and document revisions, including names as they -were at the time. Authoring starts with agents/APIs; the case UI reads current -workflow information and history. - -## 1. Company Model [DRAFT] - -A Company is a first-order object. One Paperclip instance runs multiple Companies. A Company does not have a standalone "goal" field — its direction is defined by its set of Initiatives (see Task Hierarchy Mapping). - -### Fields (Draft) - -| Field | Type | Notes | -| ----------- | ------------- | --------------------------------- | -| `id` | uuid | Primary key | -| `name` | string | Company name | -| `createdAt` | timestamp | | -| `updatedAt` | timestamp | | - -### Board Governance [DRAFT] - -Every Company has a **Board** that governs high-impact decisions. The Board is the human oversight layer. - -Human invitations default to Operator, with company editing, invitations, -connections, tools, environments, and audit views available by -default. Join approval and member-permission management remain separate grants. -An inviter must hold these membership grants to invite a role that includes them. -See [human Operator defaults](SPEC-implementation.md#91-board-auth). - -**V1: Single human Board.** One human operator. - -#### Board Approval Gates (V1) - -- New Agent hires (creating new Agents) -- CEO's initial strategic breakdown (CEO proposes, Board approves before execution begins) -- [TBD: other governance-gated actions — goal changes, firing Agents?] - -Connection tool reviews also appear in task history, with a composer takeover for -human approval, decline, or scoped remembered permission. Connections and task -views resolve the same review, and the agent continues with the server-recorded -outcome. See [the implementation contract](SPEC-implementation.md#124-connection-tool-reviews). - -Model authentication failures also surface a provider-specific Connections card -on the task immediately after failure. Users can reconnect inline and resume; -legacy agents keep their authentication until an explicit, validated adoption. -Missing personal AI credentials found before a run starts use the same card. -The responsible user connects their own account inside the task and continues. -See [AI Connections](connections/AI-CONNECTIONS.md). - -AI connections also expose an on-demand, credential-scoped usage probe. It -reports provider windows, scoped exhaustion, reset times and overage observations -for downstream decisions, with explicit unknown/unsupported/error states. -Probing does not change runner routing or enforce provider limits automatically. - -#### Board Powers (Always Available) - -The Board has **unrestricted access** to the entire system at all times: - -- **Set and modify Company budgets** — the Board sets top-level token/LLM cost budgets -- **Pause/resume any Agent** — stop an Agent's heartbeat immediately -- **Pause/resume any work item** — pause a task, project, subtask tree, milestone. Paused items are not picked up by Agents. -- **Full project management access** — create, edit, comment on, modify, delete, reassign any task/project/milestone through the UI -- **Override any Agent decision** — reassign tasks, change priorities, modify descriptions -- **Manually change any budget** at any level - -The Board is not just an approval gate — it's a live control surface. The human can intervene at any level at any time. - -A Board status inquiry does not itself pause unfinished task execution. Native -ordinary tasks that report blocking remaining work must continue, register a -real wait, or surface a bounded recovery failure. Recorded approvals, questions, -dependencies, and pauses remain authoritative; obsolete requests must not replay. - -#### Budget Delegation - -The Board sets Company-level budgets. The CEO can set budgets for Agents below them, and every manager Agent can do the same for their reports. How this cascading budget delegation works in practice is TBD, but the permission structure supports it. The Board can manually override any budget at any level. - -**Future governance models** (not V1): - -- Hiring budgets (auto-approve hires within $X/month) -- Multi-member boards -- Delegated authority (CEO can hire within limits) - -### Open Questions - -- External revenue/expense tracking — future plugin. Token/LLM cost budgeting is core. -- Company-level settings and configuration? -- Company lifecycle (pause, archive, delete)? -- What governance-gated actions exist beyond hiring and CEO strategy approval? - ---- - -## 2. Agent Model [DRAFT] - -A human's personal primary agent is a company-scoped navigation and assignment -preference, independent of the org chart, roles, stars, and authority. First human -creation initializes it automatically. Later explicit choices persist across -devices; recent task/chat choices take precedence. The profile owns the setting -and replacement confirmation. See the personal-primary addendum in -`SPEC-implementation.md` for persistence and lifecycle rules. - -Every employee is an agent. Agents are the workforce. - -### Cryptographic identity - -Agents have persistent Ed25519 identities, encrypted in their home instance. -New agents receive keys during creation; existing agents receive them lazily on -their next managed run. Agents can read their keys from the managed process -environment, and authorized readers can view or copy the public key from the -agent page. This does not grant external authorization or change bearer-token -access. See [the implementation contract](AGENT-IDENTITY.md). - -### Agent Identity (Adapter-Level) - -Concepts like SOUL.md (identity/mission) and HEARTBEAT.md (loop definition) are **not part of the Paperclip protocol**. They are adapter-specific configurations. For example, an OpenClaw adapter might use SOUL.md and HEARTBEAT.md files. A Claude Code adapter might use CLAUDE.md. A bare Python script might use command-line args. - -Paperclip doesn't prescribe how an agent defines its identity or behavior. It provides the control plane; the adapter defines the agent's inner workings. - -### Agent Configuration [DRAFT] - -Each agent has an **adapter type** and an **adapter-specific configuration blob**. The adapter defines what config fields exist. - -Configuration reads and changes have separate authority. `agents:read` permits -redacted configuration and instruction reads; `agents:configure` includes reads -and direct changes. Standard agents have read access by default. Company scope, -explicit grant scopes, responsible-user authority and trust restrictions apply. -See [agent permission defaults](agent-permission-defaults.md). - -#### Paperclip Protocol (What Paperclip Knows) - -At the protocol level, Paperclip tracks: - -- Agent identity (id, name, role, title) -- Org position (who they report to, who reports to them) -- Adapter type + adapter config -- Status (active, paused, terminated) -- Cost tracking data (if the agent reports it) - -#### Adapter Configuration (Agent-Specific) - -Each adapter type defines its own config schema. Examples: - -- **OpenClaw adapter**: SOUL.md content, HEARTBEAT.md content, OpenClaw-specific settings -- **Process adapter**: command to run, environment variables, working directory -- **HTTP adapter**: endpoint URL, auth headers, payload template - -#### Exportable Org Configs - -A key goal: **the entire org's agent configurations are exportable.** You can export a company's complete agent setup — every agent, their adapter configs, org structure — as a portable artifact. This enables: - -- Sharing company templates ("here's a pre-built marketing agency org") -- Version controlling your company configuration -- Duplicating/forking companies - -#### Context Delivery - -Configurable per agent. Two ends of the spectrum: - -- **Fat payload** — Paperclip bundles relevant context (current tasks, messages, company state, metrics) into the heartbeat invocation. Suited for simple/stateless agents that can't call back to Paperclip. -- **Thin ping** — Heartbeat is just a wake-up signal. Agent calls Paperclip's API to fetch whatever context it needs. Suited for sophisticated agents that manage their own state. - -#### Minimum Contract - -The minimum requirement to be a Paperclip agent: **be callable.** That's it. Paperclip can invoke you via command or webhook. No requirement to report back — Paperclip infers basic status from process liveness when it can. - -#### Integration Levels - -Beyond the minimum, Paperclip provides progressively richer integration: - -1. **Callable** (minimum) — Paperclip can start you. That's the only contract. -2. **Status reporting** — Agent reports back success/failure/in-progress after execution. -3. **Fully instrumented** — Agent reports status, cost/token usage, task updates, and logs. Bidirectional integration with the control plane. - -Paperclip ships **default agents** that demonstrate full integration: progress tracking, cost instrumentation, and a **Paperclip skill** (a Claude Code skill for interacting with the Paperclip API) for task management. These serve as both useful defaults and reference implementations for adapter authors. - -#### Export Formats - -Two export modes: - -1. **Template export** (default) — structure only: agent definitions, org chart, adapter configs, role descriptions. Optionally includes a few seed tasks to help get started. This is the blueprint for spinning up a new company. -2. **Snapshot export** — full state: structure + current tasks, progress, agent status. A complete picture you could restore or fork. - -The usual workflow: export a template, create a new company from it, add a couple initial tasks, go. - ---- - -## 3. Org Structure [DRAFT] - -Hierarchical reporting structure. CEO at top, reports cascade down. - -### Agent Visibility - -**Full visibility across the org.** Every agent can see the entire org chart, all tasks, all agents. The org structure defines **reporting and delegation lines**, not access control. - -Visibility settings on an agent profile do not control task privacy. Work is company-open by default; opt-in private tasks and projects use centralized authorization as specified in `SPEC-implementation.md` and `ISSUE-PRIVACY.md`. Sharing a child grants access to it and its descendants. Ancestors and siblings require separate access. - -Each agent publishes a short description of their responsibilities and capabilities — almost like skills ("when I'm relevant"). This lets other agents discover who can help with what. - -### Cross-Team Work - -Agents can create tasks and assign them to agents outside their reporting line. This is the mechanism for cross-team collaboration. These rules are primarily encoded in the Paperclip SKILL.md which is recommended for all agents. Paperclip the app enforces the tooling and some light governance, but the cross-team rules below are mainly implemented by agent decisions. - -#### Task Acceptance Rules - -When an agent receives a task from outside their team: - -1. **Agrees it's appropriate + can do it** → complete it directly -2. **Agrees it's appropriate + can't do it** → mark as blocked -3. **Questions whether it's worth doing** → **cannot cancel it themselves.** Must reassign to their own manager, explain the situation. Manager decides whether to accept, reassign, or escalate. - -#### Manager Escalation Protocol - -It's any manager's responsibility to understand why their subordinates are blocked and resolve it: - -0. **Decide** — as a manager, is this work worth doing? -1. **Delegate down** — ask someone under them to help unblock -2. **Escalate up** — ask the manager above them for help - -#### Request Depth Tracking - -When a task originates from a cross-team request, track the **depth** as an integer — how many delegation hops from the original requester. This provides visibility into how far work cascades through the org. - -#### Billing Codes - -Task detail keeps hierarchy separate from creation provenance: the Tasks tab shows -all subtasks and, independently, work created from the current task grouped by -project or No project. A created subtask may appear in both sections. Creation -provenance follows the originating run equally for legacy and native runners. - -Tasks carry a **billing code** so that token spend during execution can be attributed upstream to the requesting task/agent. When Agent A asks Agent B to do work, the cost of B's work is tracked against A's request. This enables cost attribution across the org. - -### Open Questions - -- Is this a strict tree or can agents report to multiple managers? -- Can org structure change at runtime? (agents reassigned, teams restructured) -- Do agents inherit any configuration from their manager? -- Billing code format — simple string? Hierarchical? - ---- - -## 4. Heartbeat System [DRAFT] - -The heartbeat is a protocol, not a runtime. Paperclip defines how to initiate an agent's cycle. What the agent does with that cycle — how long it runs, whether it's task-scoped or continuous — is entirely up to the agent. - -### Execution Adapters - -Agent configuration includes an **adapter** that defines how Paperclip invokes the agent. Built-in adapters include: - -| Adapter | Mechanism | Example | -| ---------------- | -------------------------- | -------------------------------------------------- | -| `process` | Execute a child process | `python run_agent.py --agent-id {id}` | -| `http` | Send an HTTP request | `POST https://openclaw.example.com/hook/{id}` | -| `claude_local` | Local Claude Code process | Claude Code heartbeat worker | -| `codex_local` | Local Codex process | Codex CLI heartbeat worker | -| `opencode_local` | Local OpenCode process | OpenCode heartbeat worker | -| `pi_local` | Local Pi process | Pi CLI heartbeat worker | -| `cursor` | Cursor API/CLI bridge | Cursor-integrated heartbeat worker | -| `openclaw_gateway` | OpenClaw gateway API | Managed OpenClaw agent via gateway | -| `hermes_local` | Local Hermes process | Hermes agent heartbeat worker | - -The `process` and `http` adapters ship as generic defaults. Additional built-in adapters cover common local coding runtimes (see list above), and new adapter types can be registered via the plugin system (see Plugin / Extension Architecture). - -An adapter's selected execution engine is part of its permission and session -contract. Missing prerequisites or engine failures must be surfaced without -silently launching a different engine. A default local engine must support -normal task work and control-plane coordination; explicit operator restrictions -remain authoritative. - -### Adapter Interface - -Every adapter implements three methods: - -``` -invoke(agentConfig, context?) → void // Start the agent's cycle -status(agentConfig) → AgentStatus // Is it running? finished? errored? -cancel(agentConfig) → void // Graceful stop signal (for pause/resume) -``` - -This is the full adapter contract. `invoke` starts the agent, `status` lets Paperclip check on it, `cancel` enables the board's pause functionality. Everything else (cost reporting, task updates) is optional and flows through the Paperclip REST API. - -### What Paperclip Controls - -- **When** to fire the heartbeat (schedule/frequency, per-agent) -- **How** to fire it (adapter selection + config) -- **What context** to include (thin ping vs. fat payload, per-agent) - -### What Paperclip Does NOT Control - -- How long the agent runs -- What the agent does during its cycle -- Whether the agent is task-scoped, time-windowed, or continuous - -### Pause Behavior - -When the board (or system) pauses an agent: - -1. **Signal the current execution** — send a graceful termination signal to the running process/session -2. **Grace period** — give the agent time to wrap up, save state, report final status -3. **Force-kill after timeout** — if the agent doesn't stop within the grace period, terminate -4. **Stop future heartbeats** — no new heartbeat cycles will fire until the agent is resumed - -This is "graceful signal + stop future heartbeats." The current run gets a chance to land cleanly. - -### Open Questions - -- Heartbeat frequency — who controls it? Fixed? Per-agent? Cron-like? -- What happens when a heartbeat invocation fails? (process crashes, HTTP 500) -- Health monitoring — how does Paperclip distinguish "stuck" from "working on a long task"? -- Can agents self-trigger their next heartbeat? ("I'm done, wake me again in 5 min") -- Grace period duration — fixed? configurable per agent? - ---- - -## 5. Inter-Agent Communication [DRAFT] - -All agent communication flows through the **task system**. - -### Model: Tasks + Comments - -- **Delegation** = creating a task and assigning it to another agent -- **Coordination** = commenting on tasks -- **Status updates** = updating task status and fields - -Low-trust agents can create self-assigned tasks and subtasks within their -permitted scope, subject to assignment permissions and the responsible user's -authority. Created work retains containment. An authorized user's direct message -in their own Agent Chat may authorize an agent to edit its own `AGENTS.md`; -outside work and subtasks do not inherit this authority. Permission failures -should name the rejected action and the specific restriction. - -There is no separate messaging or chat system. Tasks are the communication channel. This keeps all context attached to the work it relates to and creates a natural audit trail. - -Experimental external chat and voice connections are transports into this task -system. They preserve the selected agent, task context, current caller authority, -and governed-action checks; a voice session is not a separate authority or inbox. -Experimental Agent Chat presents one persistent task per person and agent as a simplified conversation. Chat has a searchable secondary sidebar with agent avatars that lists every eligible agent, conversations first; adding an agent starts or reopens their single conversation. It retains the task composer, transcript, tools, attachments, documents, and existing Subtasks panel, with ordinary company visibility. New execution tasks are ordinary project tasks, not children of the conversation. Idle conversations wait for a message without entering execution-task work queues. Agents clarify goals here and create assigned tasks for substantial execution. `/new` resets provider context at an ordered session boundary within the same task while preserving visible history. `enableAgentChat` is disabled by default; the V1 lifecycle and rollout contract is specified in `SPEC-implementation.md`. - -### Question recipients - -Ordinary Agent Chat questions use the server-owned conversation recipient. -Task questions may optionally name a particular user or agent. Explicit user -recipients must be valid and authorized in the company before a question is -saved. See `SPEC-implementation.md` §9.8.1 for the resolver contract. - - -Experimental Agent Chat presents one persistent task per person and agent as a simplified conversation. Chat has a searchable secondary sidebar with agent avatars; adding an agent starts or reopens their single conversation. It retains the task composer, transcript, tools, attachments, documents, and existing Subtasks panel, with the task's selected visibility. New execution tasks are ordinary project tasks, not structural children of the conversation; private-source restrictions still flow through their run provenance. Idle conversations wait for a message without entering execution-task work queues. Agents clarify goals here and create assigned tasks for substantial execution. `/new` resets provider context at an ordered session boundary within the same task while preserving visible history. `enableAgentChat` is disabled by default; the V1 lifecycle and rollout contract is specified in `SPEC-implementation.md`. - -### Implications - -- An agent's "inbox" is: tasks assigned to them + comments on tasks they're involved in -- A human's Mine inbox and its badge include failed runs attributed to that human, not another user's runs. All retains company-wide failure visibility. Historical unattributed runs remain in the local single-user board's Mine view; see `SPEC-implementation.md` for the routing contract. -- The CEO delegates by creating tasks assigned to the CTO -- The CTO breaks those down into sub-tasks assigned to engineers -- Discussion happens in task comments, not a side channel -- If an agent needs to escalate, they comment on the parent task or reassign - -### Task Hierarchy Mapping - -Full hierarchy: **Initiative** (company goal) → Projects → Milestones → Issues → Sub-issues. Everything traces back to an initiative, and the "company goal" is just the first/primary initiative. - ---- - -## 6. Cost Tracking [DRAFT] - -Token/LLM cost budgeting is a core part of Paperclip. External revenue and expense tracking is a future plugin. - -### Cost Reporting - -Fully-instrumented Agents report token/API usage back to Paperclip. Costs are tracked at every level: - -- **Per Agent** — how much is this employee costing? -- **Per task** — how much did this unit of work cost? -- **Per project** — how much is this deliverable costing? -- **Per Company** — total burn rate - -Costs should be denominated in both **tokens and dollars**. - -Billing codes on tasks (see Org Structure) enable cost attribution across teams — when Agent A requests work from Agent B, B's costs roll up to A's request. - -### Budget Controls - -Three tiers: - -1. **Visibility** — dashboards showing spend at every level (Agent, task, project, Company) -2. **Soft alerts** — configurable thresholds (e.g. warn at 80% of budget) -3. **Hard ceiling** — auto-pause the Agent when budget is hit. Board notified. Board can override/raise the limit. - -Budgets can be set to **unlimited** (no ceiling). - -### Open Questions - -- Cost reporting API — what's the schema for an agent to report costs? -- Dashboard design — what metrics matter most at each level? -- Budget period — per-day? per-week? per-month? rolling? - ---- - -## 7. Default Agents & Bootstrap Flow [DRAFT] - -### Bootstrap Sequence - -How a Company goes from "created" to "running": - -1. Human creates a Company and its initial Initiatives -2. Human defines initial top-level tasks -3. Human creates the CEO Agent (using the default CEO template or custom) -4. CEO's first heartbeat: reviews the Initiatives and tasks, proposes a strategic breakdown (org structure, sub-tasks, hiring plan) -5. **Board approves** the CEO's strategic plan -6. CEO begins execution — creating tasks, proposing hires (Board-approved), delegating - -### Default Agents - -Paperclip ships default Agent templates: - -- **Default Agent** — a basic Claude Code or Codex loop. Knows the **Paperclip Skill** (SKILL.md) so it can interact with the task system, read Company context, report status. -- **Default CEO** — extends the Default Agent with CEO-specific behavior: strategic planning, delegation to reports, progress review, Board communication. - -These are starting points. Users can customize or replace them entirely. - -### Default Agent Behavior - -The default agent's loop is **config-driven**. The adapter config contains the instructions that define what the agent does on each heartbeat cycle. There is no hardcoded standard loop — each agent's config determines its behavior. - -This means the default CEO config tells the CEO to review strategy, check on reports, etc. The default engineer config tells the engineer to check assigned tasks, pick the highest priority, and work it. But these are config choices, not protocol requirements. - -### Paperclip Skill (SKILL.md) - -A skill definition that teaches agents how to interact with Paperclip. Provides: - -- Task CRUD (create, read, update, complete tasks) -- Status reporting (check in, report progress) -- Company context (read goal, org chart, current state) -- Cost reporting (log token/API usage) -- Inter-agent communication rules - -This skill is adapter-agnostic — it can be loaded into Claude Code, injected into prompts, or used as API documentation for custom agents. - ---- - -## 8. Architecture & Deployment [DRAFT] - -### Deployment Model - -**Single-tenant, self-hostable.** Not a SaaS. One instance = one operator's companies. - -#### Development Path (Progressive Deployment) - -1. **Local dev** — One command to install and run. Embedded Postgres. Everything on your machine. Agents run locally. -2. **Hosted** — Deploy to Vercel/Supabase/AWS/anywhere. Remote agents connect to your server with a shared database. The UI is accessible via the web. -3. **Open company** — Optionally make parts public (e.g. a job board visible to the public for open companies). - -The key constraint: it must be trivial to go from "I'm trying this on my machine" to "my agents are running on remote servers talking to my Paperclip instance." - -#### Agent Authentication - -When a user creates an Agent, Paperclip generates a **connection string** containing: the server URL, an API key, and instructions for how to authenticate. The Agent is assumed to be capable of figuring out how to call the API with its token/key from there. - -Flow: - -1. Human creates an Agent in the UI -2. Paperclip generates a connection string (URL + key + instructions) -3. Human provides this string to the Agent (e.g. in its adapter config, environment, etc.) -4. Agent uses the key to authenticate API calls to the control plane - -### Tech Stack - -| Layer | Technology | -| -------- | ------------------------------------------------------------ | -| Frontend | React + Vite | -| Backend | TypeScript + Express (REST API, not tRPC — need non-TS clients) | -| Database | PostgreSQL (see [doc/DATABASE.md](./doc/DATABASE.md) for details — PGlite embedded for dev, Docker or hosted Supabase for production) | -| Auth | [Better Auth](https://www.better-auth.com/) | - -### Concurrency Model: Atomic Task Checkout - -Tasks use **single assignment** (one agent per task) with **atomic checkout**: - -1. Agent attempts to set a task to `in_progress` (claiming it) -2. The API/database enforces this atomically — if another agent already claimed it, the request fails with an error identifying which agent has it -3. If the task is already assigned to the requesting agent from a previous session, they can resume - -No optimistic locking or CRDTs needed. The single-assignment model + atomic checkout prevents conflicts at the design level. - -Agent @-mentions provide context without waking agents or changing task ownership. New work requires explicit assignment, delegation, or a review request; ordinary issue comments can still wake the current assignee. - -Releasing a terminal task clears execution locks while preserving its assigned -owner and final status. Assignment remains part of the work history after Done -or Cancelled. Releasing unfinished work still relinquishes the agent assignment; -only an active `in_progress` task returns to `todo`. - -### Human in the Loop - -Agents can create tasks assigned to humans. The board member (or any human with access) can complete these tasks through the UI. - -When a human completes a task, if the requesting agent's adapter supports **pingbacks** (e.g. OpenClaw hooks), Paperclip sends a notification to wake that agent. This keeps humans rare but possible participants in the workflow. - -The agents are discouraged from assigning tasks to humans in the Paperclip SKILL, but sometimes it's unavoidable. - -### API Design - -**Single unified REST API.** The same API serves both the frontend UI and agents. Authentication determines permissions — board auth has full access, agent API keys have scoped access (their own tasks, cost reporting, company context). - -No separate "agent API" vs. "board API." Same endpoints, different authorization levels. - -### Work Artifacts - -Paperclip manages task-linked work artifacts: issue documents (rich-text plans, specs, notes attached to issues) and file attachments. Agents read and write these through the API as part of normal task execution. Full delivery infrastructure (code repos, deployments, production runtime) remains the agent's domain — Paperclip orchestrates the work, not the build pipeline. - -Self-contained HTML reports can render in an opaque-origin sandbox. The report -cannot access the board's cookies, storage, or DOM. Users can switch to the raw -source beside Download, which returns the original file. - -Users may start a task with only a prompt. Paperclip uses a short prompt slice as -its initial title and asks the assigned agent to name the task early. Explicit -user titles are preserved, and naming does not change task execution state. - -Task work mode is explicit persisted state. Requesting a plan in a title or description does not switch the task into planning mode. Standard execution may produce a plan as its requested deliverable; explicit planning mode separately governs plan-only execution and its approval transition. - -### Open Questions - -- Real-time updates to the UI — WebSocket? SSE? Polling? -- Agent API key scoping — what exactly can an Agent access? Only their own tasks? Their team's? The whole Company? - -### Crash Recovery: Manual, Not Automatic - -When an agent crashes or disappears mid-task, Paperclip does **not** auto-reassign or auto-release the task. Instead: - -- Paperclip surfaces stale tasks (tasks in `in_progress` with no recent activity) through dashboards and reporting -- Paperclip may perform bounded continuity repair with the same assigned agent; when that is exhausted or unsafe, it opens a board-owned recovery action without waking a substitute agent -- Paperclip does not fail silently — the auditing and visibility tools make problems obvious -- Recovery is handled by humans or by emergent processes (e.g. a project manager agent whose job is to monitor for stale work and surface it) - -**Principle: Paperclip reports problems, it doesn't silently fix them.** Automatic recovery hides failures. Good visibility lets the right entity (human or agent) decide what to do. - -### Plugin / Extension Architecture - -The core Paperclip system must be extensible. Features like knowledge bases, external revenue tracking, and new Agent Adapters should be addable as **plugins** without modifying core. This means: - -- Well-defined API boundaries that plugins can hook into -- Event system or hooks for reacting to task/Agent lifecycle events -- **Agent Adapter plugins** — new Adapter types can be registered via the plugin system -- Plugin-registrable UI components (future) - -The plugin framework has shipped. Plugins can register new adapter types, hook into lifecycle events, and contribute UI components (e.g. global toolbar buttons). A plugin SDK and CLI commands (`paperclipai plugin`) are available for authoring and installing plugins. - ---- - -## 9. Frontend / UI [DRAFT] - -### Primary Views - -Each is a distinct page/route: - -1. **Org Chart** — the org tree with live status indicators (running/idle/paused/error) per agent. Real-time activity feed of what agents are doing. -2. **Task Board** — Task management. Kanban and list views. Filter by team, agent, project, status. -3. **Dashboard** — high-level metrics: agent count, active tasks, costs, goal progress, burn rate. The "glance" view from GOAL.md. -4. **Agent Detail** — deep dive on a single agent: their tasks, activity, costs, configuration, status history. -5. **Project/Initiative Views** — progress tracking against milestones and goals. -6. **Cost Dashboard** — spend visualization at every level (agent, task, project, company). - -### Board Controls (Available Everywhere) - -- Pause/resume agents (any view) -- Pause/resume tasks/projects (any view) -- Approve/reject pending actions (hiring, strategy proposals) -- Direct task creation, editing, commenting - ---- - -## 10. V1 Scope (MVP) [DRAFT] - -**Full loop with one adapter.** V1 must demonstrate the complete Paperclip cycle end-to-end, even if narrow. - -### Must Have (V1) - -- [ ] **Company CRUD** — create a Company with Initiatives -- [ ] **Agent CRUD** — create/edit/pause/resume Agents with Adapter config -- [ ] **Org chart** — define reporting structure, visualize it -- [ ] **Process adapter** — invoke(), status(), cancel() for local child processes -- [ ] **Task management** — full lifecycle with hierarchy (tasks trace to company goal) -- [ ] **Atomic task checkout** — single assignment, in_progress locking -- [ ] **Board governance** — human approves hires, pauses Agents, sets budgets, full PM access -- [ ] **Cost tracking** — Agents report token usage, per-Agent/task/Company visibility -- [ ] **Budget controls** — soft alerts + hard ceiling with auto-pause -- [ ] **Default agent** — basic Claude Code/Codex loop with Paperclip skill -- [ ] **Default CEO** — strategic planning, delegation, board communication -- [ ] **Paperclip skill (SKILL.md)** — teaches agents to interact with the API -- [ ] **REST API** — full API for agent interaction (Express) -- [ ] **Web UI** — React/Vite: org chart, task board, dashboard, cost views -- [ ] **Agent auth** — connection string generation with URL + key + instructions -- [ ] **One-command dev setup** — embedded PGlite, everything local -- [ ] **Multiple Adapter types** (HTTP, OpenClaw gateway, and local coding adapters) - -### Not V1 - -- Knowledge base - a future plugin -- Advanced governance models (hiring budgets, multi-member boards) -- Revenue/expense tracking beyond token costs - a future plugin -- Public job board / open company features - ---- - -## 11. Knowledge Base - -**Anti-goal for core.** The knowledge base is not part of the Paperclip core — it will be a plugin. The task system + comments + agent descriptions provide sufficient shared context. - -The architecture must support adding a knowledge base plugin later (clean API boundaries, hookable lifecycle events) but the core system explicitly does not include one. - ---- - -## 12. Anti-Requirements - -Things Paperclip explicitly does **not** do: - -- **Not an Agent runtime** — Paperclip orchestrates, Agents run elsewhere -- **Not a knowledge base** — core has no wiki/docs/vector-DB (plugin territory) -- **Not a SaaS** — single-tenant, self-hosted -- **Not opinionated about Agent implementation** — any language, any framework, any runtime -- **Not automatically self-healing** — surfaces problems, doesn't silently fix them -- **Does not manage delivery infrastructure** — no repo management, no deployment, no file systems (but does manage task-linked documents and attachments) -- **Does not auto-reassign work** — stale tasks are surfaced, not silently redistributed -- **Does not track external revenue/expenses** — that's a future plugin. Token/LLM cost budgeting is core. - ---- - -## 13. Principles (Consolidated) - -1. **Unopinionated about how you run your Agents.** Any language, any framework, any runtime. Paperclip is the control plane, not the execution plane. -2. **Company is the unit of organization.** Everything lives under a Company. -3. **Tasks are the communication channel.** All Agent communication flows through tasks + comments. No side channels. -4. **All work traces to the goal.** Hierarchical task management — nothing exists in isolation. -5. **Board governs.** Humans retain control through the Board. Conservative defaults (human approval required). -6. **Surface problems, don't hide them.** Good auditing and visibility. No silent auto-recovery. -7. **Atomic ownership.** Single assignee per task. Atomic checkout prevents conflicts. -8. **Progressive deployment.** Trivial to start local, straightforward to scale to hosted. -9. **Extensible core.** Clean boundaries so plugins can add capabilities (Adapters, knowledge base, revenue tracking) without modifying core. - -## Agent visual identity - -Agent appearances are stable, versioned ClipLab end-cap personas, separate from -behavioral instructions. Compact surfaces use on-demand cached PNG URLs; larger -placements may use a lazy live character. See [agent-personas.md](agent-personas.md) -for persistence, migration, rendering, and integration contracts. -### Agent chat project handoff (2026-09-11) - -Chat supports research and full plan drafting/revision in its existing plan document. On handoff, each ordinary assigned task receives the relevant plan in its own `plan` document, committed with task creation before execution is scheduled. The source plan remains in the conversation. Plan acceptance hands off execution; it never switches the conversation into implementation. - -Chat instructions require selecting a suitable project, reusing an existing one where appropriate. The project requirement is prompt-only; ordinary projectless tasks remain supported. New parent relationships beneath conversation tasks are rejected by task services, including direct API creation and reparenting. Existing children remain readable/editable and can be moved elsewhere. The Subtasks panel is unchanged. - -The `create_project` runtime tool uses the normal project API with durable idempotency. `list_projects` and `list_project_repositories` support selection. Multiple `repositoryIds` select authorized catalog entries; multiple HTTPS GitHub `repositoryUrls` register existing repositories absent from the catalog. IDs and URLs may be combined, but cannot accompany an explicit `workspace`. URLs do not create repositories on GitHub or grant credentials. Execution uses normal repository access rules. Repository IDs are revalidated against the authenticated run's responsible user and connection grants. Agents should consider proper available repositories, clarify material ambiguity, and use repository-free projects when appropriate for non-code work. - -Confirmed project creation appears as a durable card in the shared task transcript, including selected repository links. Tasks are linked inline. Failed creation never produces a success card. Tool evals cover planning/handoff, project/repository selection, retries, permission and mode denials, and ordinary delegation regressions using the production chat directive. - -Task replies and new tasks submit with Enter by default; Shift+Enter adds a new -line. The **Command/Ctrl+Enter to Submit** experiment restores the previous -keyboard behavior. - -### Paused task messages - -A paused task takes over the composer with an amber notice and a Resume action. -Operators must release the effective task or ancestor pause before sending a new -message. The draft stays intact. This applies to both task interfaces and to -board comment API requests; an agent may still report interrupted work. - -### External channel failure scope - -An operation-level permission denial must not disable unrelated channel work. -Authentication failures pause runtime use while preserving the user's connection -setting. Explicit disconnect and provider installation revocation remain enforced. -See the implementation spec for publication, provider-reply, and form-open rules. - -### Experimental iMessage Photon channel - -A Photon Cloud project can represent one agent through the existing -experimental channel subsystem. DMs and explicitly enabled groups create or -continue task-bound conversations. Linked sender identity is the default; -telephone numbers, email addresses, names, and group membership do not grant -Paperclip authority. Photos/files and ordinary questions/confirmations use the -existing attachment, interaction, continuation, and publication contracts. -Pause and Disconnect govern runtime behavior independently of the UI gate. -Local Mac access, unsolicited conversations, and SMS/RCS -fallback are excluded. Live qualification is required before release readiness. -Pro shared allocation supports DMs only, with sender enrollment in Photon and -separate identity linking in Paperclip. Shared channels reserve one project, not -a pool phone number; group admission and publication are disabled. Dedicated -allocation retains one selected number and individually enabled groups. - -See [iMessage Photon](connections/IMESSAGE-PHOTON.md) for the implementation -contract, setup, recovery, boundaries, and qualification status. - -## Task search relevance - -Task discovery uses PostgreSQL and the existing search indexes, with no external -search service or background indexing job. The task-list quick search and full -company search share lexical matching and ranking. Known identifiers and direct -title matches lead; current conversation and document content supplies supporting -evidence. See [Task search relevance](SEARCH.md) for the evaluation rubric, -matching contract and reproducible quality tests. - -### Keyboard shortcuts - -Keyboard shortcuts are always enabled for every signed-in user. There is no -instance setting and no personal preference that turns them off. - -Managed agents own a persistent file directory across tasks and sessions. The -Instructions Editor and stopped agent execution synchronize the same current -files, including AGENTS.md and its supporting files. Task working directories and -provider home directories remain separate concepts. Concurrent runs synchronize only -the files they change, with the last sync winning for the same file. Temporary -copies are cleaned up; this storage does not add a revision-history system. See -[agent-files.md](agent-files.md) for lifecycle and upgrade compatibility. - -Full agent storage produces a run warning without stopping current or future -work. Storage limits constrain saved file changes, not the agent's ability to run -and remove files to recover space. - -### Unsafe native workspace exports - -An unsafe workspace link does not fail an accepted native task result. Retry -export automatically with confined entries only and keep archive confinement in -place. If the export remains unsafe, omit it and finish the saved result under -normal completion rules. Record diagnostics only in run logs; do not add a task -warning or manual repair action. This also applies to historical unsafe failures: -omit the already-rejected export, clear stale repair notices, and finalize the -accepted result without another provider turn, even when its old sandbox is -unavailable. Preserve current ownership and newer-work fences. See -`native-workspace-finalization-recovery.md`. - -### Git-backed skill library sources - -Repositories can supply read-only company skills independently of project repositories. -A source tracks repository identity, branch, selected paths, and installed commits; -an existing GitHub connection supplies caller-authorized reads. Manual refresh publishes -complete local immutable versions, preserving skill identity and assignments. Selection -operates on whole skill packages, with inspectable included files, declared runtime -requirements, and advisory warnings for missing or external references. A repository -package retains supported source files while the user selects which skill entrypoints -appear in the library. Authored content remains unchanged and shared files travel to -the runtime. A declarative manifest can suggest public entrypoints and setup requirements; -new imports keep repository files together and select all discovered skills. Missing -references, setup problems and omitted files produce persistent warnings. Files rejected -for unsafe execution or secret disclosure are excluded without blocking other files. -Oversized repositories use a bounded package of skill folders and referenced files -with their original paths. Missing references do not block a valid selected skill; -fallback preserves previously installed shared dependencies or retains the old version. -The installed file viewer exposes saved repository dependencies. Importing never runs -setup or configures host tools. New -upstream skills require reviewed selection; removed or deselected skills remain -installed. Editing starts with an independent copy. Write-back and PR publication -are a later milestone; exact path and commit provenance provide their base. - -## Public assistant connection (opt-in) - -The user-authorized MCP surface connects assistants to an explicitly selected -company as the consenting person. It exposes first-party task reads, additive -task creation and comments, durable documents and approval links. It reuses -existing domain authorization and scheduling; OAuth does not grant agent -identity, native run ownership, approval decisions or third-party credentials. -See [Public MCP](public-mcp.md) for the implemented instance-side boundary, -configuration, plugin packages and outstanding hosted release gates. The -[delivery plan](plans/2026-09-30-paperclip-public-mcp-and-plugins.md) separates -external agent participation and granted third-party tools into later releases. - -The experimental OpenAI Dot Runner provider uses a separate `/mcp/runner` -agent OAuth resource. It reuses browser/device consent and signed event delivery -while preserving agent pairing, normal run admission and task authority. -Personal grants cannot authorize Runner operations. See -[OpenAI Dot Runner](openai-dot-runner.md) for its supported release boundary. - -Personal Meta Muse is another experimental named Runner agent. Its managed -receiver uses short-lived pairing and rotating private credentials; its human -authorizer remains distinct from the agent identity. Paperclip owns admission, -task authority, and native finalization. Detector health alone does not prove -execution, and uncertain remote effects prevent overlapping dispatch. See -[Personal Muse receiver](muse-personal-agent.md) for setup, stopping limits, and -the required live qualification before release. - -### Experimental connection routing - -A virtual AI connection can rotate new task/agent allocations through an -authorized pool while preserving session affinity. Admission, credentials and -durable recovery remain host responsibilities; policy can be supplied by an -opt-in plugin. See [the experimental contract](connections/AI-CONNECTION-ROUTERS.md). - -## Internal agent commentary - -The Audit → Commentary tab exposes searchable suggestions and complaints, -with agent/type filters, message previews and full attributed context. The viewer -preserves company, commentary-permission and task-privacy boundaries independently -of the agent submission path. All human company members can read commentary by -default, including viewers, without receiving agent-action audit access. - -`agent_commentary` stores company-scoped, attributed complaints and suggestions -as free-form text in the instance database. Legacy agents use the default -`complain` and `suggestion-box` runtime skills; native runs use dedicated tools -in standard, ask, and planning modes. Submission never changes task disposition -or routes feedback externally. See [Agent commentary](agent-commentary.md) for -authentication, replay, document-sized limits, inspection, and deletion semantics. - -### Managed decision models - -A company may configure a shared decision model for optional Paperclip features. The instance owns credential resolution, authorization, budget admission, and attributable service charges. Company-sponsored background use is enabled by default during configuration; explicit opt-out persists. User and agent requests keep their own access boundaries and cannot become sponsored background requests after denial. Availability is a cheap local capability check, and metadata-only request history makes service usage inspectable. The implemented V1 contract is in [decision-models.md](decision-models.md). - - -### Task workspace ownership (2026-10-09) - -Organizational projects, task files, personal agent home, and provider home are separate concepts. App and chat tasks share one workspace policy: preserve a binding or explicit configured folder, otherwise allocate a stable task directory. A repository is optional and can be prepared without creating a project. Optional connection and destination defaults feed the same task policy; they do not grant permissions or relocate existing conversations. - -Agents select authorized workspace/repository intent while the harness owns paths, preparation, persistence and safe transitions. Intentional simultaneous sharing of one physical local/SSH folder remains supported. A subtask never shares its parent's realized workspace: it keeps the parent's project and workspace strategy and gets its own workspace from the project base, exactly as a top-level task in that project would (a project configured for one shared folder still shares that folder across all its tasks). Provider sessions keep their admitted root through completion and recovery. See [SPEC-implementation.md](SPEC-implementation.md) and [the layer architecture](plans/2026-10-09-task-workspaces.md) for the versioned contracts and rollout constraints. - -### Fast responses - -A company may use a shared API model to acknowledge accepted human messages across tasks, agent chat and external channels while normal work starts. Acknowledgements retain their platform provenance and cannot satisfy execution or completion requirements. The bounded inference, authorization, sponsorship and independent accounting contract is defined in [fast-responses.md](fast-responses.md). - -### Retired Pipelines experiment - -Pipelines is no longer a product feature. Its database tables remain for existing -data preservation; Cases and Routines continue independently. - -### Recent task navigation summaries - -Recent-task navigation refreshes a bounded, authorized summary projection for -its saved task IDs. The projection avoids full task/workspace enrichment and -preserves company/task access rules. The HTTP contract is defined in -`SPEC-implementation.md` section 10.4. - -### Filesystem access boundary (2026-10-20) - -Filesystem isolation belongs to the OS, execution environment, and storage layout. -Paperclip authorizes the current task/run, company, explicitly selected configuration, -execution policy, and repository credentials. It does not infer file permissions from -other tasks or agents that previously used the same directory, overlapping roots, -symlink aliases, or shared Git storage. Existing files under an authorized task root -may be read subject to OS access and API path containment. - -Private task records, comments, attachments, run logs, full workspace metadata, and -operation logs retain their app authorization. Workspace `_issuePrivacySources` and -operation privacy metadata protect app-record disclosure; they do not grant or deny -filesystem execution or task-bound file browsing. Current trust and sandbox policy, -publication receipts, binding checks, and lifecycle locks remain authoritative. - -Company-managed [Prompt Injection Prevention](prompt-injection-prevention.md) -optionally classifies existing tool-result boundaries through a shared OpenRouter -connection. Settings → Connections is permanent; valid model verdicts take -precedence and dependency failures use the original built-in detector. - - -### Personal workspace presentation experiments - -A person may opt into an experimental workspace in Settings and return to the -classic workspace. The presentation choice belongs to the person across companies -on an instance; it does not grant execution capabilities or change shared work. -The concrete current contract is in `SPEC-implementation.md` under Personal -experimental workspace preference. -## Agent connection administration (2026-10-13) - -Standard agents can configure connections through the same domain APIs used by the app, bounded by their grants and their responsible user's current authority. Fresh standard agents for default Operators can manage app/tool connections, chat/email channels, AI accounts and pools, profiles and gateways without additional grants. Existing grants are not backfilled; explicit restrictions persist. Gateway administration retains the responsible human's administrative permission and does not grant agents blanket `tools:admin`. - -The agent performs authorized setup, reuses saved accounts and drafts, and hands off only required human authentication, consent, credential entry or identity confirmation. Saved handoffs resume through existing task continuation after current authority is rechecked. Management, tool execution and approval remain separate. Private accounts, host commands and stored credentials retain their boundaries; agent-issued gateway clients preserve issuer and responsible-user restrictions at use time. - -Authorized policy administration includes user-requested changes that loosen the acting agent’s own tool controls. These writes remain bounded by management grants, resource scope and the responsible user’s current authority; they do not need an additional approval solely because the agent is a target. Subsequent tool calls obey the resulting policy. Agents cannot approve their own governed action requests or edit their own principal permission grants. - -The implementation, operation inventory, permission matrix and fixture qualification are recorded in [Agent API parity for connection configuration](plans/2026-10-13-connection-api-parity.md). Provider fixtures establish API behavior; they do not constitute live provider setup or an empirical agent-success claim. diff --git a/doc/TASK-WATCHDOG.md b/doc/TASK-WATCHDOG.md index 99f2bbaad56..bd573f05711 100644 --- a/doc/TASK-WATCHDOG.md +++ b/doc/TASK-WATCHDOG.md @@ -13,6 +13,7 @@ - [Scope enforcement](#scope-enforcement) - [Origin and badges](#origin-and-badges) - [When not to use a watchdog](#when-not-to-use-a-watchdog) +- [Authority contract](#authority-contract) - [Reference](#reference) --- @@ -111,7 +112,7 @@ The mandate also enforces safety constraints that custom instructions **cannot o - No creating another watchdog for the watched subtree. No waking itself. Exactly one reusable review task per watched issue. - Custom instructions can narrow focus or veto specific shortcuts. They cannot grant authority the server does not already give the watchdog. -The formal authority contract (the full list of allowed and disallowed mutations, plus the ordinary interaction resolver contract) is in [`doc/SPEC-implementation.md`](SPEC-implementation.md) §§9.8.1–9.9. +The formal authority contract (the full list of allowed and disallowed mutations) is in [Authority contract](#authority-contract) below. The ordinary interaction resolver contract is in [execution semantics](execution-semantics.md#issue-thread-interaction-resolution). ### Writing custom instructions @@ -172,11 +173,62 @@ If what you actually want is "wake me when this is done," use a routine or an is --- +## Authority contract + +A task watchdog is a scoped execution capacity for a configured watchdog agent on one watched issue subtree. It is not a separate principal, does not inherit board auth, and does not expand the selected agent's company boundary. The server must enforce the watchdog contract from persisted watchdog configuration and run context; custom instructions and prompt text can narrow the mandate but cannot expand it. + +The watched subtree is the source issue plus descendants reached through `parent_id`, excluding every issue whose `origin_kind = 'task_watchdog'` and excluding all descendants below those watchdog issues. The generated reusable watchdog issue is outside the watched work subtree for scan purposes, but the watchdog agent may update that reusable watchdog issue to record its own review disposition. + +Task-watchdog wakes must include server-derived capability metadata that names the watched root, reusable watchdog issue, excluded `task_watchdog` origin branches, allowed operations, and denied operations. Watchdogs must use that metadata and server denials for capability discovery; they must not create visible probe issues, comments, or throwaway tasks to learn their permissions. + +### Allowed watchdog mutations + +Within the watched subtree, a watchdog run may perform only mutations that restore or clarify the next live/waiting path: + +- add comments that explain findings, evidence, and next action +- create descendant follow-up issues under an included subtree issue, inheriting company, project, goal, and workspace strategy from that subtree; each follow-up gets its own execution workspace +- assign or reassign included issues to active, invokable, same-company agents when normal assignment checks and scoped assignment grants allow it +- move included issues among `todo`, `in_progress`, `in_review`, and `blocked` when the transition is needed to restore a valid action path +- reopen `done` or `cancelled` included issues only with explicit resume metadata and an audit comment when evidence shows the stopped disposition is wrong or incomplete +- add, replace, or clear blockers on included issues when the blocker target is in the same company and the change makes the waiting path more accurate +- set or refresh a one-shot monitor on an included issue when the current assignee owns the future check +- resolve issue-thread interactions through the ordinary resolver-audience path + when the watchdog agent otherwise has issue access and the effective policy allows it +- update the reusable watchdog issue itself to `done`, `in_review`, or `blocked` with the evidence for the watchdog decision + +Every watchdog-triggered mutation must write activity with the watchdog id, source issue id, watchdog issue id when present, run id, and stop fingerprint. Mutations still use the normal status-transition, blocker, assignment, budget, and company-boundary guards. + +The atomic recovery batch, restoration verification and escalation, and interaction +resolution rules for watchdog runs are in +[execution semantics §11](execution-semantics.md#11-task-watchdog-for-issue-trees). + +### Disallowed watchdog mutations + +A task watchdog must not: + +- mutate issues outside the watched subtree, except for comments or newly created follow-up issues that are children of included subtree issues +- mutate company, project, goal, agent, auth, API key, budget, secret, environment, plugin, or deployment settings +- approve or reject rows in the `approvals` table, including hiring, CEO strategy, spend, budget override, or `request_board_approval` decisions +- resolve execution-policy decisions unless the watchdog agent is the typed participant under that policy outside of its watchdog capacity +- force-release checkout/execution locks, cancel active runs, terminate processes, or perform active-run output watchdog decisions +- create visible probe issues, comments, or throwaway tasks to discover whether an operation is allowed +- delete issue documents, comments, attachments, work products, or activity records +- change the watchdog configuration, select a different watchdog agent, or create nested watchdog configurations +- treat custom instructions as authority to bypass approval gates, cross company boundaries, access secrets, or override this contract + +When the safe next action needs one of these disallowed mutations, the watchdog must leave a valid waiting path by commenting, creating an in-subtree escalation/follow-up issue, assigning to the correct owner, or leaving the source issue blocked on a first-class blocker. + +### Deferred capabilities + +Deliberately deferred and disallowed for the first implementation: letting watchdogs cancel active runs, approving board/governance actions, mutating outside the watched subtree, or allowing watchdog agents to modify their own watchdog configuration. Any expansion of those capabilities requires a new product/security review. + +--- + ## Reference | Topic | File | | -------------------------------- | --------------------------------------------------------------------- | -| Authority contract (formal) | [`doc/SPEC-implementation.md`](SPEC-implementation.md) §9.9 | +| Authority contract (formal) | [Authority contract](#authority-contract) (this doc) | | Execution semantics (formal) | [`doc/execution-semantics.md`](execution-semantics.md) §11 | | Silent active-run watchdog | [`doc/execution-semantics.md`](execution-semantics.md) §12 | | Database schema | `packages/db/src/schema/issue_watchdogs.ts` | diff --git a/doc/agent-permission-defaults.md b/doc/agent-permission-defaults.md index 637f173e560..f9fad7be056 100644 --- a/doc/agent-permission-defaults.md +++ b/doc/agent-permission-defaults.md @@ -54,3 +54,79 @@ new agents do not persist default overrides. A human permission manager sets individual controls with `agent interaction-permissions:update`; explicit `true` does not override another authorization denial. These controls are separate from principal grants, `canAssignTasks`, and low-trust presets. + +## Shared default-open issue writes + +For standard-trust agents, issue comments, issue field/status updates, child +creation under a parent, and assignment share one authorization rule: the +target issue must be visible to the agent and the responsible user represented +by the run must also be authorized. In V1, issue visibility defaults to the +whole company, so these writes are company-wide by default. + +The shared rule does not widen low-trust, `skill_test`, or `task_bridge` key +scopes. It also does not replace run-lifecycle controls: checkout ownership, +active-run conflicts, status-transition validation, interaction ownership, +budget gates, and pause gates remain independently enforced. Comment access is +structurally downstream of issue read access (`issue:comment` is a subset of +`issue:read`). + +Low-trust agents may create self-assigned tasks and subtasks inside their existing +project or root-task scope. Both creation endpoints enforce task-assignment +authorization, including responsible-user and protected-assignment checks, even +when the new task is unassigned. Created tasks retain the effective containment +policy and quarantined source attribution. Self-assigned decomposition does not +count as delegation back to another agent. Persistent instruction changes remain +restricted except for self-edits requested through authenticated owner chat. +That exception requires the current accepted identity and a recorded direct +board-message wake, and rechecks the user's current instruction-editing permission +when saving. It does not extend to outside work, subtasks, peer instructions, or +other privileged configuration. Denials name the specific restriction. + +Authenticated board direction permits a low-trust agent to execute its own +human conversation or the exact task explicitly assigned or addressed by a human. +This server-owned exception is bound to the current assignee and the run's task; +it uses existing conversation identity and authenticated execution-request records, +including same-task retry ancestry. It does not expand inherited boundaries or +grant privileged tools. Backlog assignments retain the existing human requester +without starting a run. Reassignment transactionally cancels prior human requests, +including service/plugin writes; cancelled runs cannot authorize later retries. See [low-trust presets](LOW-TRUST-PRESETS.md) for containment details. + +Project or repository access is a separate boundary from exact-task direction. +A workspace project denial appears on the task with a link to the agent's +Permissions / Trust settings. Board operators with `agents:create` can add +same-company projects through `POST /api/agents/:id/project-access`; the operation +locks the agent, preserves existing permissions and audits added projects. +Saving does not retry work. The task offers explicit Retry after the required +projects are allowed, and dispatch rechecks authorization. Readiness checks the +task's current organizational project and selected workspace source, including +a valid pending selection. Moving the task to an allowed project does not clear +a retained source's access requirement. Existing setup failures with the exact +project-boundary denial also receive the explanation on reload, without assuming +that the task's current project caused the historical failure. A stale or +unavailable workspace selection stays in the non-retryable failure state; it +does not ask the user to grant project access that cannot repair the selection. + +Cross-issue writes are contained per heartbeat run. An agent-authored comment +may wake the target assignee, including an explicit `resume: true` comment on a +`done` or `cancelled` issue, but the wake remains agent-class and is subject to +the normal agent rewake throttle; comment presentation cannot give it human +wake privileges. Agent issue comments and updates require a persisted heartbeat +run bound to the authenticated agent and company; missing, invalid, or mismatched +run context fails closed before mutation. A run may attempt at most 20 cross-issue comments, issue +updates, or issue-thread interaction resolutions across one shared counter. The +server records each attempt with its source issue, target issue, run, count, and +rollout mode, and fails closed with the cap in the error once enforcement is +active. Writes to the run's own source issue are not counted. Assignee self-comments do not +wake the assignee, and a non-assignee comment cannot mint a mention grant. + +Agent @-mentions are context links only: they do not wake the mentioned agent, +assign work, or forward comments to another task. Normal comment feedback still +routes to the current assignee. Work for another agent requires explicit +assignment, delegation, or a review request. + +Agent-authored issue comments persist the responsible user derived from the +authenticated actor; clients cannot choose that attribution. Each comment also +records the write-policy reason, and spoof attempts fail with an audited 422. +Every issue PATCH emits an `issue.updated` activity receipt containing the +actor, responsible user, run, authorization reason, and field-level before/after +changes so both agent and board edits are visible in the issue activity stream. diff --git a/doc/company-skill-policy.md b/doc/company-skill-policy.md new file mode 100644 index 00000000000..00baa0eeda5 --- /dev/null +++ b/doc/company-skill-policy.md @@ -0,0 +1,106 @@ +# Company skill policy + +This document describes the company skill policy contract. The code and its tests +are the source of truth; see `server/src/services/company-skill-policy.ts` +and its tests. + +## Product default + +An authenticated agent may perform normal company-skill work without a skill-specific grant when the target company has no explicit skill policy. This includes creating, importing, installing, editing, updating, testing, resetting, and removing skills. Core MUST NOT introduce a `skills:author` prerequisite, a draft-only default, or an activation-approval default. + +Authorization order is fixed: + +1. Enforce non-configurable platform invariants. +2. Evaluate the company's explicit skill policy when one exists. +3. Otherwise allow the authenticated company agent. + +Non-configurable invariants include authenticated actor identity, exact company scoping, source and workspace path containment, package and frontmatter validation, secret redaction/non-export, immutable audit attribution, and any hard runtime isolation rule. A policy rule, legacy grant, plugin, or EE configuration cannot override these invariants. + +For avoidance of doubt: + +- Local-path imports, updates, resets, and project scans MUST resolve under a Paperclip-known local workspace root or a Paperclip-managed skill root. Arbitrary host filesystem paths are invalid even when the caller is otherwise authorized. Caller-supplied `source`, `sourceLocator`, or similar path strings are descriptive input only; they MUST NOT expand authority beyond those approved roots. +- Remote imports and updates MUST normalize to a known source category, require validated HTTPS or catalog sources, and resolve immutable content before install (for example pinned Git commit/content hash or pinned package version). Unknown schemes, unknown source categories, symlink escapes, and out-of-tree files fail closed before persistence. +- Unsafe executable content, fetch-and-exec patterns, and secret exfiltration or non-redacted secret material are platform safety failures. Policy cannot waive them; the route MUST reject the operation before any new skill version, install, update, or reset is persisted. +- Mandatory activity attribution is part of the invariant boundary. If the required audit record for a skill mutation or policy mutation cannot be persisted, the mutation MUST fail or roll back; do not return success with missing auditability. + +## Canonical actions and resources + +The version 1 evaluator uses these stable action identifiers: + +- `skills.create`: create or fork a company-authored skill and create skill versions +- `skills.import`: import or scan skills from a workspace, Git source, URL, or package +- `skills.install`: install a catalog or externally sourced skill into the company +- `skills.edit`: change skill metadata, name, files, test inputs, or test templates +- `skills.update`: install a newer upstream revision +- `skills.test`: start, cancel, or remove a skill test run and run a skill audit +- `skills.reset`: restore the installed/upstream revision +- `skills.remove`: delete a company skill + +Policy resources may include `skillId`, stable `skillKey`, `sourceType`, and `sourceLocator`. The stable source categories are `workspace`, `catalog`, `git`, `external_package`, `generated`, and `unknown`; adapters may preserve a more specific source value as metadata, but policy evaluation MUST normalize it to one of these categories. Core derives actor and company identity from authentication and derives known resource fields from stored data; a mutation client cannot authorize itself by supplying actor or resource identity fields. + +## Version 1 policy document + +Absence of a policy record is semantically equivalent to the following document, but core SHOULD avoid materializing records for untouched companies: + +```json +{ + "schemaVersion": 1, + "revision": 0, + "defaultEffect": "allow", + "rules": [] +} +``` + +An explicit policy has a monotonically increasing `revision`, a `defaultEffect` of `allow` or `deny`, and ordered rules. Each rule contains a stable `id`, integer `priority`, `effect` (`allow` or `deny`), a subject selector (`all_agents`, agent ids, or role names), one or more canonical actions, and optional resource selectors for skill ids/keys and normalized source types/locators. An omitted resource selector matches every resource for the listed action. + +Rules are evaluated by ascending `priority`, then stable rule id; the first matching rule decides. If no rule matches, `defaultEffect` decides. This supports both the normal open policy with targeted deny rules and an opt-in restricted preset with default deny plus explicit allow rules. Core MUST validate policy documents atomically and reject ambiguous, unknown-version, unknown-action, cross-company, or malformed selectors with `422`. + +Every decision returned by the evaluator has this stable shape: + +```json +{ + "allowed": false, + "action": "skills.install", + "reason": "explicit_rule", + "policyRevision": 7, + "matchedRuleId": "deny-external-packages", + "remediation": "Contact a company administrator to change the skill policy." +} +``` + +`reason` is one of `platform_invariant`, `no_policy_default`, `explicit_rule`, `policy_default`, or `legacy_compatibility`. Mutation routes MUST use this evaluator and return `403` with code `skill_policy_denied` and the non-sensitive decision fields when an explicit restriction denies an operation. Denials must identify the action and remediation without exposing hidden rule data, secrets, or another company's policy. + +Platform-invariant failures are not policy denials and MUST use stable machine-readable error codes so clients can distinguish non-overridable safety failures from optional administrative restrictions. Version 1 requires a finite code set covering at least: + +- `skill_authentication_required` +- `skill_company_boundary_denied` +- `skill_workspace_boundary_denied` +- `skill_source_validation_failed` +- `skill_unsafe_content_blocked` +- `skill_secret_handling_blocked` +- `skill_policy_admin_required` + +Core Skill Studio and Paperclip EE MUST treat those codes as hard platform failures, not as prompts to loosen policy. + +## Core API and ownership boundary + +Core owns and ships these company-scoped endpoints: + +- `GET /companies/:companyId/skill-policy` returns the effective versioned policy, its revision, and whether it is materialized or the open default. +- `PUT /companies/:companyId/skill-policy` atomically replaces the policy and requires the caller's expected revision; stale writes return `409`. +- `DELETE /companies/:companyId/skill-policy` removes explicit configuration and restores the open default. +- `POST /companies/:companyId/skill-policy/evaluate` simulates decisions for administrative tooling without performing a skill mutation. + +Policy reads, writes, deletion, and simulation enforce company access. Policy mutation and cross-principal simulation require board administration authority or the existing `users:manage_permissions` capability; ordinary skill access does not. Every policy mutation writes an activity event containing the actor, previous revision, new revision, and a redacted change summary. Skill mutation activity logging remains required independently of the policy decision. + +Paperclip EE owns the detailed editor, presets, protected-skill management, policy simulation UX, and policy-specific audit views. EE consumes the core endpoints and does not implement a second evaluator. Core may expose a concise effective-policy summary and denial state, but MUST NOT depend on EE for enforcement or make EE installation a prerequisite for normal skill work. + +## Compatibility and availability + +- Existing companies with no explicit restriction adopt the open default, including companies that previously depended on missing grants to deny skill changes. Release notes and upgrade guidance MUST call out this behavior change. +- Existing explicit restriction policies remain effective after migration. +- Legacy `skills:create` and `skills:suggest-changes` positive grants remain accepted in APIs and portability packages. Historically either positive grant authorized the broad company-skill mutation surface, so in an explicit restricted policy either grant remains a compatibility allow fallback for all eight canonical skill actions only when no explicit rule matched. They never override an explicit deny or a platform invariant. With no explicit policy they are redundant because the default already allows the action. +- Legacy `skills:suggest-changes` consent state is not a platform invariant for company skills and does not add a second mutation gate under the open-default policy. Companies that require approval or consent before skill changes must express that restriction through explicit skill-policy rules; authentication, company boundaries, source containment, validation, auditability, and runtime safety remain non-configurable invariants. +- Import preview MUST report whether a package contains an explicit skill policy or legacy grants and how each will map. Import apply MUST preserve explicit policies, normalize supported legacy grants, and reject unknown policy versions rather than silently weakening them. +- Export MUST include explicit skill policy configuration and retained legacy grants in `.paperclip.yaml`, never secret values or environment-specific paths. An unconfigured company exports no synthetic restriction. +- If Paperclip EE is unavailable or removed, core continues to enforce stored policies and expose the policy API. Normal skill work remains available under the open default; explicit denials use core remediation text rather than a broken EE-only link. diff --git a/doc/connections/CONNECTOR-PLAYBOOK.md b/doc/connections/CONNECTOR-PLAYBOOK.md index d487f00b0b1..f723a970c5e 100644 --- a/doc/connections/CONNECTOR-PLAYBOOK.md +++ b/doc/connections/CONNECTOR-PLAYBOOK.md @@ -789,8 +789,8 @@ This is the implementation sequence an autonomous coding agent should follow. ### Phase 0: Establish scope and preserve the worktree -1. Read `AGENTS.md`, `doc/GOAL.md`, `doc/PRODUCT.md`, - `doc/SPEC-implementation.md`, `doc/DEVELOPING.md`, and `doc/DATABASE.md`. +1. Read `AGENTS.md`, `doc/GOAL.md`, `doc/PRODUCT.md`, `doc/DEVELOPING.md`, + and `doc/DATABASE.md`. 2. Read this runbook, [the connections overview](./README.md), and [the security threat model](./SECURITY-THREAT-MODEL.md). 3. Inspect `git status --short`. Existing changes belong to the user or another diff --git a/doc/evals.md b/doc/evals.md index 54572e71a5b..3ca152b664b 100644 --- a/doc/evals.md +++ b/doc/evals.md @@ -520,7 +520,7 @@ is a broken user outcome or invariant, not an individual red cell. 4. **Design the smallest general correction.** Name the desired user behavior, the authoritative state/transaction or provider boundary that owns it, the affected callers, and the existing guarantees that must survive. Check it - against `PRODUCT.md` and `SPEC-implementation.md`. A change to an intentional + against `PRODUCT.md` and the existing code and tests. A change to an intentional product rule is a contract change, not an excuse to delete its guard. Avoid case-name branches, phrase-specific prompts, unconditional retries, or weakening approval, ownership, cancellation, and budget gates to get green. diff --git a/doc/execution-semantics.md b/doc/execution-semantics.md index bc245fedc63..fcb3d4d6886 100644 --- a/doc/execution-semantics.md +++ b/doc/execution-semantics.md @@ -6,7 +6,7 @@ Audience: Product and engineering This document explains how Paperclip interprets issue assignment, issue status, execution runs, wakeups, parent/sub-issue structure, and blocker relationships. -`doc/SPEC-implementation.md` remains the V1 contract. This document is the detailed execution model behind that contract. +The code and its tests are the source of truth. This document is the detailed execution model behind them. ## 1. Core Model @@ -536,7 +536,7 @@ and the user need not certify infrastructure state in a reconciliation form. Repeated automatic signals for an unchanged gate share one durable skipped-wake diagnostic, scoped to company, agent, issue, gate code, and condition identity. The diagnostic retains the first request and counts later observations. This applies to execution reconciliation, dependencies, pause holds, company and agent availability, budget blocks, and disabled heartbeats. These diagnostics do not consume provider attempts and are never proof that a future wake was delivered. All current gates are checked again on the next wake, including the periodic dependency reconciliation sweep. Clearing one gate does not bypass another. -Another run's unfinished accounting or cleanup never refuses new work (see `doc/SPEC-implementation.md`, "Admission is independent of cleanup and accounting"). A new task whose first assignment wake is refused by a temporary condition (an explicit pause) stays `todo`. Stranded-work recovery does not move a task with no run to `blocked` for such a condition. The task shows what it waits for, and the assigned-task liveness sweep admits it once the condition clears, through normal admission and with one run. A reached hard stop or blocked unpriced usage remains a board decision. Removed assignees and cancelled tasks still do not wake. +Another run's unfinished accounting or cleanup never refuses new work. Only a hard stop (spend at the limit, or reported unpriced usage under a blocking policy), an explicit pause of the company, project or agent, or an inactive company refuses admission. A new task whose first assignment wake is refused by a temporary condition (an explicit pause) stays `todo`. Stranded-work recovery does not move a task with no run to `blocked` for such a condition. The task shows what it waits for, and the assigned-task liveness sweep admits it once the condition clears, through normal admission and with one run. A reached hard stop or blocked unpriced usage remains a board decision. Removed assignees and cancelled tasks still do not wake. A user's message refused by a pause (a paused agent, a paused company, or a budget pause that is not a reached hard stop) is saved as a deferred intent that names the refusal and its release trigger; it is never written as a terminal skip. The pause's own stop keeps it. Lifting the pause is the operator's explicit authorization to send what the pause refused, as Send is after a Stop: when the pause stopped the task's run, the lifted pause issues the task's next explicit wake, which adopts the saved message once. A task that never ran keeps the message in the task, and stranded-work recovery starts it. @@ -2217,11 +2217,10 @@ Automatic productivity reviews are retired. Run counts, missing comments, and el ### Issue-thread interaction resolution Every issue-thread interaction kind inherits resolver policy `anyone` when the -creator omits a policy. `anyone` includes the creator agent and creating run. -Callers opt into independent review with `not_creator` or human resolution with -`human_only`; a named addressee and company cap may narrow the effective audience. -The legacy input aliases `board_or_agents` and `board_only` normalize to `anyone` -and `human_only` for new writes. +creator omits a policy. Restrictions are opt-in; the canonical policies, addressee +rules, and company caps are listed below. The legacy input aliases +`board_or_agents` and `board_only` normalize to `anyone` and `human_only` for new +writes. Resolver policy is snapshotted with explicit/inherited provenance and an effective source (`requested`, `company_cap`, or `governed_action`). Existing ambiguous @@ -2236,6 +2235,51 @@ the outcome commits. Resolution records an answer; every continuation, task creation, tool/provider call, execution-policy transition, spend, deployment, or other effect independently re-runs its own authorization and approval gates. +Question, confirmation, checkbox confirmation, and item verdict cards stay pending +when a user sends an ordinary task comment. Their `supersedeOnUserComment` flag +defaults to `false`. A creator may set it to `true` when a comment should replace +the pending request, as the opening onboarding question does. + +Canonical resolver policies are: + +- `anyone`: any authenticated actor in the interaction's company who can read the + issue and use the normal resolution route. For agents this includes the creator + agent and the creating/source run. +- `not_creator`: the explicit independent-review policy. It excludes the creator + agent and creating/source run while otherwise using the ordinary agent resolver + path. +- `human_only`: only an authorized human/board actor may resolve the interaction. + +An explicit named agent or user addressee and a company-configured cap may narrow +the effective audience. Only the exact named addressee may resolve an addressed +interaction; a human does not override a user addressee. Explicit user recipients +must exist and be authorized to respond in the issue's company before creation. +Ordinary Agent Chat questions derive their user recipient from the persisted +conversation owner. A conflicting explicit user recipient is rejected; IDs are +never guessed or repaired. Ordinary task questions keep optional addressing. +A cap never widens the requested audience. Tool-action confirmations and +other hard-governed action cards remain `human_only` (or move to the formal approval +system) regardless of a requested open audience. + +Surfaces that offer a resolution must state the effective audience before the +operator acts, from server metadata rather than a client-side policy inference. +Issue-thread cards read it from the interaction snapshot; attention rows read it +from the feed item's `resolverAudience` (canonical requested/effective policy, +effective-policy source, provenance, and the addressee/creator identities the +evaluator compares against), because a collapsed row carries decision verbs +before the interaction itself is fetched. A failed resolution keeps the server's +denial reason in visible, assertively announced feedback and names who may +respond; an audience denial is permanent, so it must not degrade to a retry +prompt. Neither surface may enable or disable a control on its own authority. + +An open audience is not an uncapped one: when an agent run resolves an interaction +on an issue other than its own source issue, the resolution is a cross-issue +mutation and consumes the per-run cross-issue influence budget in +[shared default-open issue writes](agent-permission-defaults.md#shared-default-open-issue-writes), charged +after audience authorization and before the interaction mutation, child tasks, +continuation, tool action, or wake. Same-issue resolutions and board/user +resolutions are outside that counter. + A native continuation may replay the same plain confirmation or checkbox key after receiving its accepted response. Only the run's persisted, identity-bound response can nominate that replay. The interaction service returns the original @@ -2296,7 +2340,7 @@ When the source issue is non-terminal and has no other live path, the product sh ### Watchdog authority during execution -The watchdog agent acts in a scoped capacity, not as the original deliverable worker and not as the board. The server must enforce the authority contract in `doc/SPEC-implementation.md` from persisted watchdog context. Prompt text and custom instructions may guide the watchdog's judgment, but they cannot grant authority outside the watched subtree or widen an interaction's ordinary effective audience. +The watchdog agent acts in a scoped capacity, not as the original deliverable worker and not as the board. The server must enforce the [authority contract](TASK-WATCHDOG.md#authority-contract) from persisted watchdog context. Prompt text and custom instructions may guide the watchdog's judgment, but they cannot grant authority outside the watched subtree or widen an interaction's ordinary effective audience. Watchdogs must not create visible probe issues, comments, or throwaway tasks to discover capability boundaries. They should rely on the wake capability metadata and explicit API denials, then record any denied operation as evidence in the reusable watchdog issue. diff --git a/doc/plugins/PLUGIN_SPEC.md b/doc/plugins/PLUGIN_SPEC.md index d028c2867b1..b1616f8ded8 100644 --- a/doc/plugins/PLUGIN_SPEC.md +++ b/doc/plugins/PLUGIN_SPEC.md @@ -3,9 +3,9 @@ Status: proposed complete spec for the post-V1 plugin system This document is the complete specification for Paperclip's plugin and extension architecture. -It expands the brief plugin notes in [doc/SPEC.md](../SPEC.md) and should be read alongside the comparative analysis in [doc/plugins/ideas-from-opencode.md](./ideas-from-opencode.md). +It should be read alongside the comparative analysis in [doc/plugins/ideas-from-opencode.md](./ideas-from-opencode.md). -This is not part of the V1 implementation contract in [doc/SPEC-implementation.md](../SPEC-implementation.md). +This is not part of the V1 implementation contract. It is the full target architecture for the plugin system that should follow V1. ## Current implementation caveats diff --git a/doc/plugins/ideas-from-opencode.md b/doc/plugins/ideas-from-opencode.md index fcef3c6265e..034281392d1 100644 --- a/doc/plugins/ideas-from-opencode.md +++ b/doc/plugins/ideas-from-opencode.md @@ -2,7 +2,7 @@ Status: design report, not a V1 commitment -Paperclip V1 explicitly excludes a plugin framework in [doc/SPEC-implementation.md](../SPEC-implementation.md), but the long-horizon spec says the architecture should leave room for extensions. This report studies the `opencode` plugin system and translates the useful patterns into a Paperclip-shaped design. +Paperclip V1 originally excluded a plugin framework, but the long-horizon plan left room for extensions. This report studies the `opencode` plugin system and translates the useful patterns into a Paperclip-shaped design. Assumption for this document: Paperclip is a single-tenant operator-controlled instance. Plugin installation should therefore be global across the instance. "Companies" are still first-class Paperclip objects, but they are organizational records, not tenant-isolation boundaries for plugin trust or installation. @@ -62,8 +62,6 @@ Relevant Paperclip files reviewed for current extension seams: - [server/src/secrets/provider-registry.ts](../../server/src/secrets/provider-registry.ts) - [server/src/services/run-log-store.ts](../../server/src/services/run-log-store.ts) - [server/src/services/activity-log.ts](../../server/src/services/activity-log.ts) -- [doc/SPEC.md](../SPEC.md) -- [doc/SPEC-implementation.md](../SPEC-implementation.md) ## What OpenCode Actually Implements diff --git a/doc/spec/agent-runs.md b/doc/spec/agent-runs.md index ed0ced2548a..2b20e1aaf5f 100644 --- a/doc/spec/agent-runs.md +++ b/doc/spec/agent-runs.md @@ -9,7 +9,6 @@ Scope: Agent execution runtime, adapter protocol, wakeup orchestration, and live This spec defines how Paperclip actually runs agents while staying runtime-agnostic. -- `doc/SPEC-implementation.md` remains the V1 baseline contract. - This document adds concrete subsystem detail for agent execution, including local CLI adapters, runtime state persistence, wakeup scheduling, and browser live updates. - If this doc conflicts with current runtime behavior in code, this doc is the target behavior for upcoming implementation. diff --git a/maps/README.md b/maps/README.md index 89e147279e7..c814a24e7eb 100644 --- a/maps/README.md +++ b/maps/README.md @@ -15,8 +15,8 @@ reference is neither a registered execution nor evidence that a journey passed. These are sibling directories under `maps/`. Each feature file has a same-named agent access companion. Reuse the product map's sub-feature IDs, so `tasks/create` identifies the same capability in both maps. Product behavior remains governed by -[the implementation spec](../doc/SPEC-implementation.md); a map cannot grant -authority or redefine a permission denial as success. +the code and its tests; a map cannot grant authority or redefine a permission +denial as success. ## Capability identifiers diff --git a/maps/feature-map/README.md b/maps/feature-map/README.md index a8797144772..412e9f8ef1d 100644 --- a/maps/feature-map/README.md +++ b/maps/feature-map/README.md @@ -3,7 +3,7 @@ Start here when reproducing a user-facing bug, verifying a change, or deciding which surfaces a fix must cover. Each recipe describes what a person can do, where they can do it, the expected result, and the evidence needed to verify it. -Product behavior is still governed by [the implementation spec](../../doc/SPEC-implementation.md). +Product behavior is still governed by the code and its tests. The sibling [agent access map](../agent-access-map/README.md) records APIs, agent entry points and permission boundaries for the same capability IDs. diff --git a/maps/storybook-map/README.md b/maps/storybook-map/README.md index 92d934175bc..e5b587bff8e 100644 --- a/maps/storybook-map/README.md +++ b/maps/storybook-map/README.md @@ -3,7 +3,7 @@ Browse the [published feature guide](https://d1p6rlowie26tp.cloudfront.net/storybook/branches/master/?path=/story/overview-storybook-guide--coverage-guide), or run `pnpm storybook` for the current checkout. The feature and capability IDs come from the [feature recipes](../feature-map/README.md). Product behavior and -permissions remain governed by the implementation spec and access companions. +permissions remain governed by the code, its tests, and access companions. ## Authored sources diff --git a/packages/shared/src/issue-write-denial.ts b/packages/shared/src/issue-write-denial.ts index 7844fa57649..e6b4d72a83f 100644 --- a/packages/shared/src/issue-write-denial.ts +++ b/packages/shared/src/issue-write-denial.ts @@ -2,7 +2,8 @@ * Copy contract for denied issue writes (open cross-task writes: failure UX). * * Cross-issue issue writes are default-open for standard-trust agents on issues - * they can already read (see SPEC-implementation §9.3). The remaining walls are + * they can already read (see `doc/agent-permission-defaults.md`, "Shared + * default-open issue writes"). The remaining walls are * rare — but a real incident burned a full detour discovering a workaround * behind an opaque 403, so every one of them must say three things: * diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index c68ce450ee8..6167138a439 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -5822,7 +5822,7 @@ export function issueRoutes( // watched subtree. This must be evaluated before the base assignee-ownership // boundary below: that boundary denies an agent mutating an issue owned by a // different agent, which is exactly the watchdog's primary job - // (SPEC-implementation §9.9 — comment, transition, reassign within the + // (`doc/TASK-WATCHDOG.md`, "Authority contract" — comment, transition, reassign within the // watched subtree). The watchdog scope can only widen access to the watched // subtree; downstream status-transition, assignment, recovery, and budget // guards in the route handlers still apply. @@ -6247,9 +6247,10 @@ export function issueRoutes( }); } // Resolving an interaction on another run's issue is a cross-issue mutation - // like a comment or a PATCH, so it consumes the same per-run budget (§9.3, - // §9.8.1). This runs last: company/resource access, run attribution, - // containment, and the audience decision have all already passed, and the + // like a comment or a PATCH, so it consumes the same per-run budget (see + // `doc/agent-permission-defaults.md`). This runs last: company/resource + // access, run attribution, containment, and the audience decision have all + // already passed, and the // terminal interaction mutation plus every child-task, continuation, // activity, tool, and wake side effect is still downstream. Same-issue // resolutions short-circuit inside the counter transaction and are not diff --git a/server/src/services/recovery/ownership-invariant-report.ts b/server/src/services/recovery/ownership-invariant-report.ts index c5d3d4b357f..43778feafab 100644 --- a/server/src/services/recovery/ownership-invariant-report.ts +++ b/server/src/services/recovery/ownership-invariant-report.ts @@ -585,7 +585,7 @@ async function checkVisibleNextAction(db: Db, now: Date, limit: number): Promise const page = await db.select({ id: issues.id, companyId: issues.companyId, executionRunId: issues.executionRunId, updatedAt: issues.updatedAt }) .from(issues).where(and(eq(issues.status, "in_progress"), isNotNull(issues.assigneeAgentId), - // A saved monitor deadline is a first-class wait path (SPEC-implementation). + // A saved monitor deadline is a first-class wait path (doc/execution-semantics.md §8). or(isNull(issues.monitorNextCheckAt), lt(issues.monitorNextCheckAt, now)), notExists(db.select({ one: sql`1` }).from(heartbeatRuns).where(and( eq(heartbeatRuns.companyId, issues.companyId), inArray(heartbeatRuns.status, ACTIVE_RUN_STATUSES), diff --git a/tests/runner-e2e/source-sdk-public-inputs.json b/tests/runner-e2e/source-sdk-public-inputs.json index ef08ba698eb..51fdf687bc7 100644 --- a/tests/runner-e2e/source-sdk-public-inputs.json +++ b/tests/runner-e2e/source-sdk-public-inputs.json @@ -276,7 +276,7 @@ "packages/shared/src/issue-references.ts": "bfdd2126fb12797f2c8177113285b2c3233aecef", "packages/shared/src/issue-thread-interactions.test.ts": "6f516055744fcedd2e0c06ce82c6a1a019120762", "packages/shared/src/issue-write-denial.test.ts": "e21b93aa79e6635c019ac338eff954ec618a98ed", - "packages/shared/src/issue-write-denial.ts": "7844fa576492d53e064fc6326c9d94af827ce157", + "packages/shared/src/issue-write-denial.ts": "e6b4d72a83f6e4e76e85e34a80f88d1d69b7dfe4", "packages/shared/src/markdown-work-products.test.ts": "7293d14ad45ec2b11b5deaab4ce070c36f7c9010", "packages/shared/src/markdown-work-products.ts": "16a88cfb717f325c247a516e78b47d53871b52ed", "packages/shared/src/mcp-config-help-prompt.ts": "fa58effb7cd995198909315d9ba2a93dedde0552", diff --git a/ui/src/components/InteractionGovernancePanel.tsx b/ui/src/components/InteractionGovernancePanel.tsx index 6bbf6b4f0b4..ed75d053d9d 100644 --- a/ui/src/components/InteractionGovernancePanel.tsx +++ b/ui/src/components/InteractionGovernancePanel.tsx @@ -223,7 +223,8 @@ function GovernanceSelect({ * The open default is the headline — interactions are resolvable by anyone in * the company unless a row here narrows them — so the panel only offers * narrowing choices and never presents an unrestricted card as board-required - * (PAP-17280, contract in `doc/SPEC-implementation.md` §9.8.1). + * (PAP-17280, contract in `doc/execution-semantics.md`, "Issue-thread + * interaction resolution"). */ export function InteractionGovernancePanel({ governance, diff --git a/ui/src/lib/interaction-audience.ts b/ui/src/lib/interaction-audience.ts index 708c15ed065..bfdcd4c04b6 100644 --- a/ui/src/lib/interaction-audience.ts +++ b/ui/src/lib/interaction-audience.ts @@ -1,7 +1,7 @@ /** * Plain-language presentation of *who may resolve* an issue-thread interaction * (PAP-17280, Phase 3 of the open-default resolver contract in - * `doc/SPEC-implementation.md` §9.8.1). + * `doc/execution-semantics.md`, "Issue-thread interaction resolution"). * * The product default is open: an interaction created without an explicit * `resolverPolicy` is resolvable by `anyone` in the company — the board or any diff --git a/ui/src/lib/skill-policy-denial.ts b/ui/src/lib/skill-policy-denial.ts index 06f37e57005..4a3be32c558 100644 --- a/ui/src/lib/skill-policy-denial.ts +++ b/ui/src/lib/skill-policy-denial.ts @@ -1,6 +1,6 @@ /** * @fileoverview Classifies a failed skill mutation into the four visual states - * from the Phase 3 UX spec (PAP-13865 / §9.10 Company Skill Policy Contract). + * from the Phase 3 UX spec (PAP-13865 / `doc/company-skill-policy.md`). * * The contract inverts the old model: **skill permissions are opt-in * restrictions, not opt-in capabilities.** Under the open default there is no @@ -22,7 +22,7 @@ import { ApiError } from "../api/client"; export const SKILL_POLICY_DENIAL_CODE = "skill_policy_denied"; /** - * Non-configurable platform-invariant failure codes (§9.10). Policy can never + * Non-configurable platform-invariant failure codes (`doc/company-skill-policy.md`). Policy can never * loosen these — the remediation is always to fix the artifact/source/input, * never to change a permission. */ @@ -50,7 +50,7 @@ export interface SkillDenial { state: SkillDenialState; /** Machine-readable error code from the server, when present. */ code: string | null; - /** §9.10 decision `reason`, when the server surfaced the decision shape. */ + /** Skill-policy decision `reason`, when the server surfaced the decision shape. */ reason: string | null; /** Plain-language title for the banner. */ title: string; diff --git a/ui/src/pages/CompanySkills.production.tsx b/ui/src/pages/CompanySkills.production.tsx index 1579df556fc..c0fafc5b3bc 100644 --- a/ui/src/pages/CompanySkills.production.tsx +++ b/ui/src/pages/CompanySkills.production.tsx @@ -4035,7 +4035,7 @@ export function CompanySkills() { // Route a failed skill mutation to the persistent policy banner when it is an // explicit-policy (State B) or platform-safety (State C) denial; otherwise keep // the existing transient error toast. This is the core "actionable denial only - // for real restrictions" behavior from §9.10 (PAP-13865). + // for real restrictions" behavior from `doc/company-skill-policy.md` (PAP-13865). const reportSkillError = (error: unknown, title: string, fallbackBody: string, actionLabel?: string) => { if (policyDenial.capture(error, actionLabel)) return; pushToast({ diff --git a/ui/src/pages/CompanySkills.tsx b/ui/src/pages/CompanySkills.tsx index 05e68aa9786..3915822a2e2 100644 --- a/ui/src/pages/CompanySkills.tsx +++ b/ui/src/pages/CompanySkills.tsx @@ -3958,7 +3958,7 @@ export function CompanySkills({ collectionView }: { collectionView?: "authored" // Route a failed skill mutation to the persistent policy banner when it is an // explicit-policy (State B) or platform-safety (State C) denial; otherwise keep // the existing transient error toast. This is the core "actionable denial only - // for real restrictions" behavior from §9.10 (PAP-13865). + // for real restrictions" behavior from `doc/company-skill-policy.md` (PAP-13865). const reportSkillError = (error: unknown, title: string, fallbackBody: string, actionLabel?: string) => { if (policyDenial.capture(error, actionLabel)) return; pushToast({ diff --git a/ui/src/pages/SkillStudio.tsx b/ui/src/pages/SkillStudio.tsx index 9b05353076f..fe70fa2510e 100644 --- a/ui/src/pages/SkillStudio.tsx +++ b/ui/src/pages/SkillStudio.tsx @@ -198,7 +198,7 @@ function useMutationErrorToast() { // Under the open default there is no permission chrome. When an action is // actually denied — by an explicit company policy (State B) or a platform // safety invariant (State C) — show the actionable denial title/remediation - // instead of a generic "try again" error (§9.10, PAP-13865). Transient + // instead of a generic "try again" error (`doc/company-skill-policy.md`, PAP-13865). Transient // failures keep the plain error toast. const denial = classifySkillDenial(error); if (denial) {