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
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.
Paperclip V1 must provide a full control-plane loop for autonomous agents:
- A human board creates a company and defines goals.
- The board creates and manages agents in an org tree.
- Agents receive and execute tasks via heartbeat invocations.
- All work is tracked through tasks/comments with audit visibility.
- Token/cost usage is reported and budget limits can stop work.
- The board can intervene anywhere (pause agents/tasks, override decisions).
Success means one operator can run a small AI-native company end-to-end with clear visibility and control.
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 | Company-scoped visibility: board + all in-company agents can see all work objects by default; public/private deployment flags affect external exposure only and do not imply project/issue privacy |
| Communication | Tasks + comments only (no separate chat system) |
| 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 and resolve only eligible task-level plan confirmations; it is not board authority, active-run output monitoring, or general liveness recovery. |
| Recovery | Liveness/watchdog recovery preserves explicit ownership: retry lost execution continuity where safe, 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 | Soft alerts + hard limit 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.
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_URLis unset
V1 implementation extends this baseline into a company-centric, governance-aware control plane.
- 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
- 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.
server/: REST API, auth, orchestration servicesui/: Board operator interfacepackages/db/: Drizzle schema, migrations, DB clients (Postgres)packages/shared/: Shared API types, validators, constants
- 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)
- local default:
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.
All core tables include id, created_at, updated_at unless noted.
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.
iduuid pknametext not nulldescriptiontext nullstatusenum:active | paused | archivedpause_reasontext nullpaused_attimestamptz nullissue_prefixtext not nullissue_counterint not nullbudget_monthly_centsint not null default 0spent_monthly_centsint not null default 0attachment_max_bytesint not nullrequire_board_approval_for_new_agentsboolean not null default false- feedback sharing consent fields
- branding fields such as
brand_color
Invariant: every business record belongs to exactly one company.
iduuid pkcompany_iduuid fkcompanies.idnot nullnametext not nullroletext not nulltitletext nullicontext nullstatusenum:active | paused | idle | running | error | pending_approval | terminatedreports_touuid fkagents.idnullcapabilitiestext nulladapter_typetext; built-ins includeprocess,http,claude_local,codex_local,gemini_local,opencode_local,pi_local,cursor,hermes_local,hermes_gateway, andopenclaw_gatewayadapter_configjsonb not nullruntime_configjsonb not null default{}; may include Paperclip runtime policy such asmodelProfiles.cheap.adapterConfigfor an optional low-cost model lane that does not change the primary adapter configdefault_environment_iduuid fkenvironments.idnullcontext_modeenum:thin | fatdefaultthinbudget_monthly_centsint not null default 0spent_monthly_centsint not null default 0- pause fields:
pause_reason,paused_at permissionsjsonb not null default{}last_heartbeat_attimestamptz nullmetadatajsonb null
Invariants:
- agent and manager must be in same company
- no cycles in reporting tree
terminatedagents cannot be resumed
iduuid pkagent_iduuid fkagents.idnot nullcompany_iduuid fkcompanies.idnot nullnametext not nullkey_hashtext not nulllast_used_attimestamptz nullrevoked_attimestamptz null
Invariant: plaintext key shown once at creation; only hash stored.
iduuid pkcompany_iduuid fk not nulltitletext not nulldescriptiontext nulllevelenum:company | team | agent | taskparent_iduuid fkgoals.idnullowner_agent_iduuid fkagents.idnullstatusenum:planned | active | achieved | cancelled
Invariant: at least one root company level goal per company.
iduuid pkcompany_iduuid fk not nullgoal_iduuid fkgoals.idnullnametext not nulldescriptiontext nullstatusenum:backlog | planned | in_progress | completed | cancelledlead_agent_iduuid fkagents.idnulltarget_datedate nullenvjsonb 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.
iduuid pkcompany_iduuid fk not nullproject_iduuid fkprojects.idnullproject_workspace_iduuid fkproject_workspaces.idnullgoal_iduuid fkgoals.idnullparent_iduuid fkissues.idnulltitletext not nulldescriptiontext nullstatusenum:backlog | todo | in_progress | in_review | done | blocked | cancelledpriorityenum:critical | high | medium | lowreview_policynullable enum:anyone | not_creator | human_only; null is equivalent toanyoneassignee_agent_iduuid fkagents.idnullassignee_user_idtext null- checkout/execution locks:
checkout_run_id,execution_run_id,execution_agent_name_key,execution_locked_at created_by_agent_iduuid fkagents.idnullcreated_by_user_iduuid fkusers.idnull- identifier fields:
issue_number,identifier - origin fields:
origin_kind,origin_id,origin_run_id,origin_fingerprint request_depthint not null default 0work_modetext not null defaultstandard; 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; accepted-plan continuations remain planning-specific and create child issues from the approved plan.
billing_codetext nullassignee_adapter_overridesjsonb nullexecution_policyjsonb nullexecution_statejsonb null- execution workspace fields:
execution_workspace_id,execution_workspace_preference,execution_workspace_settings started_attimestamptz nullcompleted_attimestamptz nullcancelled_attimestamptz nullhidden_attimestamptz null
Invariants:
- single assignee only
- task must trace to company goal chain via
goal_id,parent_id, or project-goal linkage in_progressrequires assignee- terminal states:
done | cancelled
iduuid pkcompany_iduuid fk not nullissue_iduuid fkissues.idnot nullauthor_agent_iduuid fkagents.idnullauthor_user_iduuid fkusers.idnullbodytext not null
iduuid pkcompany_iduuid fk not nullagent_iduuid fk not nullinvocation_sourceenum:scheduler | manual | callbackstatusenum:queued | running | succeeded | failed | cancelled | timed_outstarted_attimestamptz nullfinished_attimestamptz nullerrortext nullexternal_run_idtext nullcontext_snapshotjsonb null
iduuid pkcompany_iduuid fk not nullagent_iduuid fkagents.idnot nullissue_iduuid fkissues.idnullproject_iduuid fkprojects.idnullgoal_iduuid fkgoals.idnullbilling_codetext nullprovidertext not nullmodeltext not nullcost_statustext not null defaultreported;unpricedwhen usage exists but no price was reportedinput_tokensint not null default 0output_tokensint not null default 0cost_centsint not nulloccurred_attimestamptz not null
Invariant: each event must attach to agent and company; rollups are aggregation, never manually edited.
iduuid pkcompany_iduuid fk not nulltypeenum:hire_agent | approve_ceo_strategy | budget_override_required | request_board_approvalrequested_by_agent_iduuid fkagents.idnullrequested_by_user_iduuid fkusers.idnullstatusenum:pending | revision_requested | approved | rejected | cancelledpayloadjsonb not nulldecision_notetext nulldecided_by_user_iduuid fkusers.idnulldecided_attimestamptz null
iduuid pkcompany_iduuid fk not nullactor_typeenum:agent | user | systemactor_iduuid/text not nullactiontext not nullentity_typetext not nullentity_iduuid/text not nulldetailsjsonb nullcreated_attimestamptz not null default now()
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:
iduuid pkcompany_iduuid fkcompanies.idnot nullproject_iduuid fkprojects.idnot nulluser_idtext not nullstateenum-like text:joined | leftcreated_attimestamptz not null default now()updated_attimestamptz 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
joinedfor backward compatibility. - Mutations are board-user-only
/meoperations; 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.joinedorresource_membership.leftactivity entries.
- Secret values are not stored inline in
agents.adapter_config.env. - Agent env entries should use secret refs for sensitive values.
company_secretstracks identity/provider metadata per company.company_secret_versionsstores 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.
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)approvals(company_id, status, type)activity_log(company_id, created_at desc)assets(company_id, created_at desc)assets(company_id, object_key)uniqueissue_attachments(company_id, issue_id)company_secrets(company_id, name)uniquecompany_secret_versions(secret_id, version)uniqueproject_memberships(company_id, user_id)project_memberships(company_id, user_id, project_id)uniqueagent_memberships(company_id, user_id)agent_memberships(company_id, user_id, agent_id)unique
assetsstores provider-backed object metadata (not inline bytes):iduuid pkcompany_iduuid fk not nullproviderenum/text (local_disk | s3)object_keytext not nullcontent_typetext not nullbyte_sizeint not nullsha256text not nulloriginal_filenametext nullcreated_by_agent_iduuid fk nullcreated_by_user_iduuid/text fk null
issue_attachmentslinks assets to issues/comments:iduuid pkcompany_iduuid fk not nullissue_iduuid fk not nullasset_iduuid fk not nullissue_comment_iduuid 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/openPathfor inline-safe viewing anddownloadPathfor forced download. - Inline-safe responses use
Content-Disposition: inline; unsafe types and explicit download requests useattachment. - Video attachments are inline-safe and support single
Range: bytes=start-endrequests with206,Content-Range, andAccept-Ranges: bytesfor browser playback/seeking.
- Default upload allowlist includes common images, PDF, plain text/markdown/JSON/CSV/HTML, ZIP, and video artifacts (
- Attachment-backed artifact work products use
type: "artifact",provider: "paperclip", and metadata withattachmentId,contentType,byteSize,contentPath,openPath,downloadPath, and optionaloriginalFilename. - Workspace-only file references use work product
metadata.resourceRefwithkind: "workspace_file",issueId,workspaceKind(execution_workspaceorproject_workspace),workspaceId,relativePath, optionalline/column, anddisplayPath. These references point at files in a workspace; they do not replace attachment-backed artifacts for deliverables that must be inspectable without workspace access.
documentsstores editable text-first documents:iduuid pkcompany_iduuid fk not nulltitletext nullformattext not null (markdown)latest_bodytext not nulllatest_revision_iduuid nulllatest_revision_numberint not nullcreated_by_agent_iduuid fk nullcreated_by_user_iduuid/text fk nullupdated_by_agent_iduuid fk nullupdated_by_user_iduuid/text fk nulllocked_attimestamptz nulllocked_by_agent_iduuid fk nulllocked_by_user_iduuid/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_revisionsstores append-only history:iduuid pkcompany_iduuid fk not nulldocument_iduuid fk not nullrevision_numberint not nullbodytext not nullchange_summarytext null
issue_documentslinks documents to issues with a stable workflow key:iduuid pkcompany_iduuid fk not nullissue_iduuid fk not nulldocument_iduuid fk not nullkeytext not null (plan,design,notes, etc.)
The current implementation includes additional V1-control-plane tables beyond the original February snapshot:
- Issue structure and review:
issue_relationsfor 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, androutine_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_queuesstores durable named queues, optional retention overrides, server-derived creator/run provenance, and data-backed seed rules.decision_queue_itemskeys membership by(queue_id, source_kind, source_id)and repeatscompany_idfor company-consistent joins.decision_triagekeys current decide-by/snooze state by(company_id, source_kind, source_id)and preserves the latest setter attribution.decision_triage_eventsis the immutable mutation history for queue membership and triage overrides, including actor, run, API-key, and responsible-user provenance.decision_retentionstores the attention source's last observed activity timestamp, monotonic version, Keep flag, and reversible archive provenance. Queueretention_daysoverrides use the shortest assigned queue threshold; otherwise the shelf threshold is 30 days.decision_archive_notification_outboxrecords 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.
Allowed transitions:
idle -> runningrunning -> idlerunning -> errorerror -> idleidle -> pausedrunning -> paused(requires cancel flow)paused -> idle* -> terminated(board only, irreversible)
Allowed transitions:
backlog -> todo | cancelledtodo -> in_progress | blocked | cancelledin_progress -> in_review | blocked | done | cancelledin_review -> in_progress | done | cancelledblocked -> todo | in_progress | cancelled- terminal:
done,cancelled
Side effects:
- entering
in_progresssetsstarted_atif null - entering
donesetscompleted_at - entering
cancelledsetscancelled_at
V1 non-terminal liveness rule:
- agent-owned
todo,in_progress,in_review, andblockedissues must have a live execution path, an explicit waiting path, or an explicit recovery path in_reviewis 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 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
- 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_progresssolely because the exiting heartbeat started a local/background watcher - invalid external-wait recovery queues at most one normal-model continuation per source-state fingerprint, then requires a real blocker or explicit recovery action instead of repeating equivalent recovery wakes; new durable source activity may establish a new fingerprint
- 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
- source-scoped recovery routing is cause-keyed: lost processes, missing successful-run dispositions, and output-inactivity terminations retry the original agent when invokable; provider-quota failures create/reuse a scheduled wait-recovery monitor without a takeover wake; workspace validation and unknown causes route to the manager ladder
- recovery-scoped wakes replace the normal deliverable execution contract with a cause-specific recovery contract, and successful repair returns the issue to the recorded original owner by default while recording
handed_backversusowner_completed
Detailed ownership, execution, blocker, active-run watchdog, crash-recovery, and non-terminal liveness semantics are documented in doc/execution-semantics.md.
pending -> approved | rejected | cancelled- terminal after decision
- Session-based auth for human operator
- Board has full read/write across all companies in deployment
- Every board mutation writes to
activity_log
- 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
| Action | Board | Agent |
|---|---|---|
| Create company | yes | no |
| Hire/create agent | yes (direct) | request via approval |
| Pause/resume agent | yes | no |
| 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 | scoped inbox:manage grant |
| Set work-object visibility (issue/project) | no | no (pro gate) |
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).
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 or issue
updates across the 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. Assignee self-comments do not
wake the assignee, and a non-assignee comment cannot mint a mention grant.
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.
Paperclip V1 keeps a company-scoped visibility model as the default because centralized authorization and scoped work-object controls are not yet a core V1 control surface.
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).
- A private marker on an agent profile (where represented) does not make company-visible work private.
- Company-visible work objects (issues, comments, work products, costs, activity, project/task state) remain visible to the board and in-company agents by default.
- Project/issue-level privacy, scoped assignment-only object visibility, and organization-wide custom ACLs are deferred to Pro/Enterprise controls.
| 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 | All issues and projects in-company are visible to board and agents | Project/issue 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; cross-user access requires 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 |
- Lock route-level checks for existing company boundaries, actor extraction, and approval/budget gates.
- Treat profile privacy as external-facing signal only; do not use it to hide company-visible work objects.
- Enforce assignment/invocation coupling (
assignee/agentchecks, checkout semantics, invocation checks). - Standardize read-path redaction for secrets and secret references, including logs and activity.
- Standardize escalation paths (
blockedand refusal) so non-board agents hand off by manager/board with immutable audit.
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, orallow: ["project:<projectId>"]. - Target-agent allowlist:
agentId,agentIds,assigneeAgentId,assigneeAgentIds,targetAgentId,targetAgentIds, orallow: ["agent:<agentId>"]. - Managed-subtree scope:
managerAgentId,managerAgentIds,managedSubtreeAgentId,managedSubtreeAgentIds,subtreeAgentId,subtreeAgentIds,subtreeRootAgentId,subtreeRootAgentIds, orallow: ["subtree:<agentId>"].
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.
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.
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 context from that subtree
- 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, andblockedwhen the transition is needed to restore a valid action path - reopen
doneorcancelledincluded 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
- accept or reject eligible task-level plan confirmations as defined below
- update the reusable watchdog issue itself to
done,in_review, orblockedwith 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.
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.
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.
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
approvalstable, including hiring, CEO strategy, spend, budget override, orrequest_board_approvaldecisions - 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.
The initial V1 watchdog resolver may resolve exactly one interaction family: request_confirmation interactions that are eligible task-level plan confirmations. The watchdog may accept a coherent eligible plan or reject/request changes with a reason. It may not resolve request_checkbox_confirmation, ask_user_questions, suggest_tasks, linked approvals, board approvals, or ad hoc document comments.
A plan confirmation is eligible only when all of these are true:
- the interaction is pending and belongs to an issue inside the watched subtree, excluding the reusable watchdog issue and its descendants
- the interaction target is an
issue_documentwith keyplanon that same issue, and the target revision is still current - the interaction has an explicit plan-approval purpose marker; title text, body prose, or idempotency key shape alone is not enough
- accepting the plan authorizes decomposition or task-level continuation inside the watched subtree only
- the plan does not request hiring, budget/spend approval, secret access, production deployment, security-sensitive policy changes, legal/compliance decisions, destructive data changes, cross-company work, or any other board-only governed action
- no newer board/user comment, document revision, superseding interaction, custom instruction, or issue policy reserves the decision for a human, CTO, Security, or the board
- the plan names concrete child/follow-up work, owners or assignee selection criteria, dependencies/blockers, and acceptance criteria clearly enough that decomposition can proceed without further judgment
If any condition fails, the watchdog must not accept the interaction. It should reject with a reason when the plan is clearly invalid, or leave/escalate the decision when the right owner is a board user, CTO, Security, or another typed approver.
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 only eligible
request_confirmationplan confirmations are accepted or rejected, and all other interaction kinds remain unavailable to watchdogs - plan-confirmation tests cover stale document revisions, missing purpose markers, outside-subtree targets, governed actions, newer user comments, and explicit human/CTO/Security reservations
- 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_watchdogorigin 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: resolving interaction kinds beyond eligible plan confirmations, 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 requires a new product/security review.
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:
- Enforce non-configurable platform invariants.
- Evaluate the company's explicit skill policy when one exists.
- 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.
The version 1 evaluator uses these stable action identifiers:
skills.create: create or fork a company-authored skill and create skill versionsskills.import: import or scan skills from a workspace, Git source, URL, or packageskills.install: install a catalog or externally sourced skill into the companyskills.edit: change skill metadata, name, files, test inputs, or test templatesskills.update: install a newer upstream revisionskills.test: start, cancel, or remove a skill test run and run a skill auditskills.reset: restore the installed/upstream revisionskills.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.
Absence of a policy record is semantically equivalent to the following document, but core SHOULD avoid materializing records for untouched companies:
{
"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:
{
"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_requiredskill_company_boundary_deniedskill_workspace_boundary_deniedskill_source_validation_failedskill_unsafe_content_blockedskill_secret_handling_blockedskill_policy_admin_required
Core Skill Studio and Paperclip EE MUST treat those codes as hard platform failures, not as prompts to loosen policy.
Core owns and ships these company-scoped endpoints:
GET /companies/:companyId/skill-policyreturns the effective versioned policy, its revision, and whether it is materialized or the open default.PUT /companies/:companyId/skill-policyatomically replaces the policy and requires the caller's expected revision; stale writes return409.DELETE /companies/:companyId/skill-policyremoves explicit configuration and restores the open default.POST /companies/:companyId/skill-policy/evaluatesimulates 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.
- 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:createandskills:suggest-changespositive 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-changesconsent 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.
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
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
disabledorallowlist. 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 an explicit
inbox:managegrant. Grants may be unscoped or constrained byscope.userIds. - Archive and unarchive operations are company-scoped, reversible, and activity logged with actor, agent, run, target user, target-resolution source, and policy mode.
- 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 a user-authored comment, issue creation/assignment, or another audited user mutation; explicit product actions such as manually running a routine may record an audited inbox touch.
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.
All endpoints are under /api and return JSON.
GET /companiesPOST /companiesGET /companies/:companyIdPATCH /companies/:companyIdPATCH /companies/:companyId/brandingPOST /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.
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.
GET /companies/:companyId/goalsPOST /companies/:companyId/goalsGET /goals/:goalIdPATCH /goals/:goalIdDELETE /goals/:goalId(soft delete optional, hard delete board-only)
GET /companies/:companyId/agentsPOST /companies/:companyId/agentsGET /agents/:agentIdPATCH /agents/:agentIdPOST /agents/:agentId/pausePOST /agents/:agentId/resumePOST /agents/:agentId/terminatePOST /agents/:agentId/keys(create API key)POST /agents/:agentId/heartbeat/invoke
GET /companies/:companyId/issuesPOST /companies/:companyId/issuesGET /issues/:issueIdPATCH /issues/:issueIdGET /issues/:issueId/documentsGET /issues/:issueId/documents/:keyPUT /issues/:issueId/documents/:keyPOST /issues/:issueId/documents/:key/lockPOST /issues/:issueId/documents/:key/unlockGET /issues/:issueId/documents/:key/revisionsDELETE /issues/:issueId/documents/:keyPOST /issues/:issueId/checkoutPOST /issues/:issueId/releasePOST /issues/:issueId/admin/force-release(board-only lock recovery)POST /issues/:issueId/commentsGET /issues/:issueId/commentsPOST /companies/:companyId/issues/:issueId/attachments(multipart upload)GET /issues/:issueId/attachmentsGET /attachments/:attachmentId/contentDELETE /attachments/:attachmentId
POST /issues/:issueId/checkout request:
{
"agentId": "uuid",
"expectedStatuses": ["todo", "backlog", "blocked", "in_review"]
}Server behavior:
- single SQL update with
WHERE id = ? AND status IN (?) AND (assignee_agent_id IS NULL OR assignee_agent_id = :agentId) - if updated row count is 0, return
409with current owner/status - successful checkout sets
assignee_agent_id,status = in_progress, andstarted_at
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.
GET /companies/:companyId/projectsPOST /companies/:companyId/projectsGET /projects/:projectIdPATCH /projects/:projectId
GET /companies/:companyId/resource-memberships/mePUT /companies/:companyId/resource-memberships/me/projects/:projectIdPUT /companies/:companyId/resource-memberships/me/agents/:agentId
Request payload:
{ "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.
GET /companies/:companyId/approvals?status=pendingPOST /companies/:companyId/approvalsPOST /approvals/:approvalId/approvePOST /approvals/:approvalId/reject
POST /companies/:companyId/cost-eventsGET /companies/:companyId/costs/summaryGET /companies/:companyId/costs/by-agentGET /companies/:companyId/costs/by-projectPATCH /companies/:companyId/budgetsPATCH /agents/:agentId/budgets
GET /companies/:companyId/activityGET /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
400validation error401unauthenticated403unauthorized404not found409state conflict (checkout conflict, invalid transition)422semantic rule violation500server error
The current app also exposes V1-supporting surfaces for:
- issue thread interactions (
suggest_tasks,ask_user_questions,request_confirmation) - issue approvals, issue references/search, labels, read state, inbox/archive state, and work products
- company search through
GET /companies/:companyId/searchplus agent-oriented bulk extraction throughGET /companies/:companyId/search/extract; extraction accepts a server-escaped literalcontains, optional server-owned URL expansion, issue/comment/document scopes, status/date filters, issue-level pagination, a boundedmatchesPerIssueoverride for machine consumers, and explicit issue/match truncation flags - execution workspaces, project workspaces, workspace runtime services, and workspace operations. Workspace reads
derive
deliveryStateasmerged_via_pr | merged_by_ancestry | unmerged | unknown; terminal issue trees with a merged delivery and no active checkout run become cleanup-eligible with reasonissue_terminaland are archived through the workspace cleanup path. Reopening the source issue records activity but does not restore that workspace. - 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-queuesPATCH /companies/:companyId/decision-queues/:keyGET|POST /companies/:companyId/decision-queues/:key/itemsDELETE /companies/:companyId/decision-queues/:key/items/:sourceKind/:sourceIdGET /companies/:companyId/decision-queue-seed-rulesGET|PUT /companies/:companyId/decision-triage/:sourceKind/:sourceIdPATCH /companies/:companyId/decision-retention/:sourceKind/:sourceId(Keep)POST /companies/:companyId/decision-retention/:sourceKind/:sourceId/archive|revivePOST /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.
interface AgentAdapter {
invoke(agent: Agent, context: InvocationContext): Promise<InvokeResult>;
status(run: HeartbeatRun): Promise<RunStatus>;
cancel(run: HeartbeatRun): Promise<void>;
}Config shape:
{
"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
Config shape:
{
"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
thin: send IDs and pointers only; agent fetches context via APIfat: include current assignments, goal summary, budget snapshot, and recent comments
The optional modelProfiles.cheap lane is not a retry worker lane. Paperclip may request the cheap profile only for status-only recovery coordination, and those wakes must include guard context that prevents deliverable work and document/plan updates (allowDeliverableWork: false, allowDocumentUpdates: false, resumeRequiresNormalModel: true).
Failed source-work retries, process-loss retries, transient/scheduled retries, max-turn continuations, source-assignee continuations, and downstream source-work child/requeue/resume contexts must use the normal/original model lane. If cheap recovery repairs liveness while actual work remains, the next live continuation path must be a separate normal-model worker run with cheap hints scrubbed.
Per-agent schedule fields in adapter_config:
enabledbooleanintervalSecinteger (minimum 30)maxConcurrentRunsinteger; new agents default to20; scheduler clamps configured values to1..50
Scheduler must skip invocation when:
- agent is paused/terminated
- an existing run is active
- hard budget limit has been hit
- Agent or board creates
approval(type=hire_agent, status=pending, payload=agent draft). - Board approves or rejects.
- On approval, server creates agent row and initial API key (optional).
- 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.
- CEO posts strategy proposal as
approval(type=approve_ceo_strategy). - Board reviews payload (plan text, initial structure, high-level tasks).
- 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.
Board can at any time:
- pause/resume/terminate any agent
- reassign or cancel any task
- edit budgets and limits
- approve/reject/cancel pending approvals
- company monthly budget
- agent monthly budget
- optional project budget (if configured)
- soft alert default threshold: 80%
- hard limit: at 100%, trigger:
- set agent status to
paused - block new checkout/invocation for that agent
- emit high-priority activity event
- set agent status to
Board may override by raising budget or explicitly resuming agent.
POST /companies/:companyId/cost-events body:
{
"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
Read-time aggregate queries are acceptable for V1. Materialized rollups can be added later if query latency exceeds targets.
V1 UI routes:
/dashboard/companiescompany list/create/companies/:id/orgorg chart and agent status/companies/:id/taskstask list/kanban/companies/:id/agents/:agentIdagent detail/companies/:id/costscost and budget dashboard/companies/:id/approvalspending/history approvals/companies/:id/activityaudit/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
- Node 20+
DATABASE_URLoptional- if unset, auto-use embedded PostgreSQL under
~/.paperclip/instances/default/db
- Drizzle migrations are source of truth
- local/dev startup applies pending migrations automatically where supported
pnpm db:migrateapplies pending migrations manually- no destructive migration in-place for V1 upgrade path
- structured logs (JSON in production)
- request ID per API call
- every mutation writes
activity_log
- 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)
- store only hashed agent API keys
- redact secrets in logs (
adapter_config, auth headers, env vars) - CSRF protection for board session endpoints
- rate limit auth and key-management endpoints
- strict company boundary checks on every entity fetch/mutation
- state transition guards (agent, issue, approval)
- budget enforcement rules
- adapter invocation/cancel semantics
- atomic checkout conflict behavior
- approval-to-agent creation flow
- cost ingestion and rollup correctness
- pause while run is active (graceful cancel then force kill)
- 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
A release candidate is blocked unless these pass:
- auth boundary tests
- checkout race test
- hard budget stop test
- agent pause/resume test
- dashboard summary consistency test
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.
- add
companiesand company scoping to existing entities - add board session auth and agent API keys
- migrate existing API routes to company-aware paths
- implement atomic checkout endpoint
- implement issue comments and lifecycle guards
- implement approvals table and hire/strategy workflows
- implement adapter interface
- ship
processadapter with cancel semantics - ship
httpadapter with timeout/error handling - persist heartbeat runs and statuses
- implement cost events ingestion
- implement monthly rollups and dashboards
- enforce hard limit auto-pause
- add company selector and org chart view
- add approvals and cost pages
- full integration/e2e suite
- seed/demo company templates for local testing
- release checklist and docs update
V1 is complete only when all criteria are true:
- A board user can create multiple companies and switch between them.
- A company can run at least one active heartbeat-enabled agent.
- Task checkout is conflict-safe with
409on concurrent claims. - Agents can update tasks/comments and report costs with API keys only.
- Board can approve/reject hire and CEO strategy requests in UI.
- Budget hard limit auto-pauses an agent and prevents new invocations.
- Dashboard shows accurate counts/spend from live DB data.
- Every mutation is auditable in activity log.
- App runs with embedded PostgreSQL by default and with external Postgres via
DATABASE_URL.
- 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)
V1 supports company import/export using a portable package contract:
- markdown-first package rooted at
COMPANY.md - implicit folder discovery by convention
.paperclip.yamlsidecar for Paperclip-specific fidelity- canonical base package is vendor-neutral and aligned with
docs/companies/companies-spec.md - common conventions:
agents/<slug>/AGENTS.mdteams/<slug>/TEAM.mdprojects/<slug>/PROJECT.mdprojects/<slug>/tasks/<slug>/TASK.mdtasks/<slug>/TASK.mdskills/<slug>/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.mdentries userecurring: truein 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 asrepoUrl, 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