## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - A company accumulates real state — issues, labels, blockers, documents, work products, monitors, attachments, agents, routines — and people need to move that state between instances: self-hosted to cloud, cloud back to self-hosted, or plain backups > - The experimental, flag-gated Cloud Sync transport (#6548) tried to solve this host-to-host: the source pushed into a receiver over HTTPS with a cross-instance consent/token handshake, which required the destination to be publicly reachable and broke for common self-hosted topologies (plain-HTTP LAN/VPN origins); the receiver half never landed upstream at all > - Meanwhile the portability bundle and the existing export/import pages already move companies offline with none of those networking constraints — but silently dropped labels, blockers, issue documents, work products, monitors, and every attachment > - This pull request removes the host-to-host transport and makes Import/Export the single data-movement path: the pages become first-class company-settings destinations, exports declare exactly what they do not carry, and bundle schemaVersion 6 now carries all of the above, with attachments as content-addressed sha256 blobs verified before a single row is written > - The benefit is a migration and backup flow that works between any two instances with no reachability requirements, no cross-instance auth, and no silent data loss ## Linked Issues or Issue Description - Refs #6548 — the original Cloud Sync sender this PR supersedes and removes. - Related, not duplicates: #1697 (goals in the portability manifest — orthogonal field addition), #954 (an earlier import/export + skill-visibility proposal predating the current portability bundle). - No open issue describes this directly, so in brief (feature-request shape): **Problem** — moving a company between instances silently lost labels (imports with label references actually hard-failed), blocker relations, issue documents, work products, monitor state, and all attachments, and the alternative Cloud Sync transport required the destination to be publicly reachable over HTTPS plus a consent handshake, which failed for typical self-hosted setups. **Desired behavior** — one Import/Export flow in company settings that produces a portable bundle carrying all of that data, tells the operator up front what it cannot carry, imports with automations paused, and offers real one-click activation afterwards. ## What Changed - New export fidelity report (`GET /api/companies/:companyId/export/fidelity`) + an "Export fidelity" panel on the Export page listing anything a bundle will not include (now only: approvals, cost history, activity history) - Imports accept `pauseAutomations`; imported agents and routines land paused, the import result reports created routines, and the Import page ends in an activation panel that actually resumes selected agents/activates routines - Export and Import pages promoted into the company-settings nav; the Cloud Upstream wizard, ux-lab page, and API client removed; the old settings route redirects to Export - Host-to-host transport removed: upstream-sync/receiver-client routes and services, CLI `cloud connect`/`cloud push` + keypair store, the shared upstream transfer contract, and the `enableCloudSync` flag; migration `0196` drops the two experimental `cloud_upstream_*` sender tables - Bundle schemaVersion 6: labels (definitions + per-task names, remapped by name on import), blocker relations (`blockedBy` slugs, cycle-tolerant), issue documents (`tasks/<slug>/documents/<key>.md`), work products (system refs nulled), monitors (notes/scheduledBy restored, imported un-armed) - Attachments travel as content-addressed `blobs/<sha256>` entries (deduped; comment-scoped attachments re-link via comment index); every blob is hash-verified **before any write**, so a corrupted bundle cannot leave a partially imported company; both zip codecs now round-trip extensionless/binary entries byte-exactly; the Import page preflights the inline body limit and offers continue-without-attachments - v5 (and older) bundles still import, with an informational warning; bundles newer than v6 are rejected cleanly - Docs: board-operator import/export guide, CLI README, README/ROADMAP updated ## Verification - `pnpm -r` typechecks (shared, db incl. migration numbering/safety checks, server, ui, cli) and `pnpm check:token-gates` — clean - Vitest: full server + shared sweep 4,888 passed / 1 skipped, with the only 3 failures being pre-existing on `master` (2× heartbeat-workspace-branch-containment, 1× workspace-runtime auto-port; reproduced identically with this change stashed); ui + cli suites green; the embedded-Postgres export-fidelity suite applies the full migration chain including the new `0196` against a fresh database - Live end-to-end on a scratch instance: seeded a company with labels, a blocker pair, an issue document, a work product, a monitor, an agent, a routine, and two binary attachments (one comment-scoped) → export → import into a fresh company → labels remapped to new ids, blocker edge and document restored, monitor un-armed with notes intact, attachments byte-identical (sha256-compared through the API), agents/routines paused → activation panel resumed them; a v5-shaped bundle imported with only the info warning; flipping one byte in a blob made the import 422 with **zero** rows created - Reviewer repro: create a company with a labeled issue + attachment → Settings → Export → download → Settings → Import on another company/instance → watch the preview, apply with "start paused", then activate ## Risks - Migration `0196` drops `cloud_upstream_connections`/`cloud_upstream_runs` — experimental tables behind a default-off flag; their connection/run history is intentionally discarded - Breaking removals are all of experimental, flag-gated surface: `/api/upstream-sync/*` + `/api/cloud-upstreams/*` routes, `paperclipai cloud connect|push`, and the `enableCloudSync` flag (stale keys in stored instance settings parse harmlessly) - Import remains non-atomic on mid-apply errors generally (pre-existing behavior); the new blob verification specifically moved ahead of all writes so tampered bundles cannot create partial state - GitHub-sourced imports do not fetch `blobs/*` and skip attachments with a warning ## Model Used - Claude Fable 5 (`claude-fable-5`, Anthropic), via Claude Code CLI with extended thinking, tool use, and subagent orchestration; implementation and review split across Fable 5 subagents, with live end-to-end verification against a running instance ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge
9.0 KiB
title, summary
| title | summary |
|---|---|
| Importing & Exporting Companies | Export companies to portable packages and import them from local paths or GitHub |
Paperclip companies can be exported to portable markdown packages and imported from local directories or GitHub repositories. This lets you share company configurations, duplicate setups, and version-control your agent teams.
Package Format
Exported packages follow the Agent Companies specification and use a markdown-first structure:
my-company/
├── COMPANY.md # Company metadata
├── agents/
│ ├── ceo/AGENT.md # Agent instructions + frontmatter
│ └── cto/AGENT.md
├── projects/
│ └── main/PROJECT.md
├── skills/
│ └── review/SKILL.md
├── tasks/
│ └── onboarding/TASK.md
└── .paperclip.yaml # Adapter config, env inputs, routines
- COMPANY.md defines company name, description, and metadata.
- AGENT.md files contain agent identity, role, and instructions.
- SKILL.md files are compatible with the Agent Skills ecosystem.
- .paperclip.yaml holds Paperclip-specific config (adapter types, env inputs, budgets) as an optional sidecar.
Export & Import in the App
Both flows are also available in the web UI as company settings pages: Export and Import appear in the company settings navigation.
The Export page lets you pick exactly which files go into the bundle before downloading it. Above the file tree it shows a "Not included in this export" panel — the export fidelity report — listing data the bundle will not carry (for example attachments, approvals, cost history, or activity log entries), with blocking issues highlighted.
The Import page previews the package, lets you resolve name collisions and adapter assignments, and applies the import. A "Start imported agents and routines paused" checkbox (on by default) makes imported agents and routines land paused instead of live. After the import finishes, an "Activate imported agents and routines" panel lists everything that was imported paused so you can resume the agents and activate the routines you select — nothing starts running until you say so.
Exporting a Company
Export a company into a portable folder:
paperclipai company export <company-id> --out ./my-export
Options
| Option | Description | Default |
|---|---|---|
--out <path> |
Output directory (required) | — |
--include <values> |
Comma-separated set: company, agents, projects, issues, tasks, skills |
company,agents |
--skills <values> |
Export only specific skill slugs | all |
--projects <values> |
Export only specific project shortnames or IDs | all |
--issues <values> |
Export specific issue identifiers or IDs | none |
--project-issues <values> |
Export issues belonging to specific projects | none |
--expand-referenced-skills |
Vendor skill file contents instead of keeping upstream references | false |
Examples
# Export company with agents and projects
paperclipai company export abc123 --out ./backup --include company,agents,projects
# Export everything including tasks and skills
paperclipai company export abc123 --out ./full-export --include company,agents,projects,tasks,skills
# Export only specific skills
paperclipai company export abc123 --out ./skills-only --include skills --skills review,deploy
What Gets Exported
- Company name, description, and metadata
- Agent names, roles, reporting structure, and instructions
- Project definitions and workspace config
- Task/issue descriptions (when included)
- Skill packages (as references or vendored content)
- Adapter type and env input declarations in
.paperclip.yaml
Secret values, machine-local paths, and database IDs are never exported.
Importing a Company
Import from a local directory, GitHub URL, or GitHub shorthand:
# From a local folder
paperclipai company import ./my-export
# From a GitHub URL
paperclipai company import https://github.com/org/repo
# From a GitHub subfolder
paperclipai company import https://github.com/org/repo/tree/main/companies/acme
# From GitHub shorthand
paperclipai company import org/repo
paperclipai company import org/repo/companies/acme
Options
| Option | Description | Default |
|---|---|---|
--target <mode> |
new (create a new company) or existing (merge into existing) |
inferred from context |
--company-id <id> |
Target company ID for --target existing |
current context |
--new-company-name <name> |
Override company name for --target new |
from package |
--include <values> |
Comma-separated set: company, agents, projects, issues, tasks, skills |
auto-detected |
--agents <list> |
Comma-separated agent slugs to import, or all |
all |
--collision <mode> |
How to handle name conflicts: rename, skip, or replace |
rename |
--ref <value> |
Git ref for GitHub imports (branch, tag, or commit) | default branch |
--dry-run |
Preview what would be imported without applying | false |
--yes |
Skip the interactive confirmation prompt | false |
--json |
Output result as JSON | false |
Target Modes
new— Creates a fresh company from the package. Good for duplicating a company template.existing— Merges the package into an existing company. Use--company-idto specify the target.
If --target is not specified, Paperclip infers it: if a --company-id is provided (or one exists in context), it defaults to existing; otherwise new.
Collision Strategies
When importing into an existing company, agent or project names may conflict with existing ones:
rename(default) — Appends a suffix to avoid conflicts (e.g.,ceobecomesceo-2).skip— Skips entities that already exist.replace— Overwrites existing entities. Only available for non-safe imports (not available through the CEO API).
Interactive Selection
When running interactively (no --yes or --json flags), the import command shows a selection picker before applying. You can choose exactly which agents, projects, skills, and tasks to import using a checkbox interface.
Preview Before Applying
Always preview first with --dry-run:
paperclipai company import org/repo --target existing --company-id abc123 --dry-run
The preview shows:
- Package contents — How many agents, projects, tasks, and skills are in the source
- Import plan — What will be created, renamed, skipped, or replaced
- Env inputs — Environment variables that may need values after import
- Warnings — Potential issues like missing skills or unresolved references
Imported agents always land with timer heartbeats disabled. Assignment/on-demand wake behavior from the package is preserved, but scheduled runs stay off until a board operator re-enables them.
Imports can additionally request pauseAutomations (the default in the app's Import page) so imported agents and routines land fully paused. Use the post-import activation panel — or resume the agents and activate the routines individually — when you are ready for them to run.
Common Workflows
Clone a company template from GitHub:
paperclipai company import org/company-templates/engineering-team \
--target new \
--new-company-name "My Engineering Team"
Add agents from a package into your existing company:
paperclipai company import ./shared-agents \
--target existing \
--company-id abc123 \
--include agents \
--collision rename
Import a specific branch or tag:
paperclipai company import org/repo --ref v2.0.0 --dry-run
Non-interactive import (CI/scripts):
paperclipai company import ./package \
--target new \
--yes \
--json
API Endpoints
The CLI commands use these API endpoints under the hood:
| Action | Endpoint |
|---|---|
| Export company | POST /api/companies/{companyId}/export |
| Export fidelity report | GET /api/companies/{companyId}/export/fidelity |
| Preview import (existing company) | POST /api/companies/{companyId}/imports/preview |
| Apply import (existing company) | POST /api/companies/{companyId}/imports/apply |
| Preview import (new company) | POST /api/companies/import/preview |
| Apply import (new company) | POST /api/companies/import |
Import apply requests accept pauseAutomations: true to create imported agents and routines in a paused state.
CEO agents can also use the safe import routes (/imports/preview and /imports/apply) which enforce non-destructive rules: replace is rejected, collisions resolve with rename or skip, and issues are always created as new.
GitHub Sources
Paperclip supports several GitHub URL formats:
- Full URL:
https://github.com/org/repo - Subfolder URL:
https://github.com/org/repo/tree/main/path/to/company - Shorthand:
org/repo - Shorthand with path:
org/repo/path/to/company
Use --ref to pin to a specific branch, tag, or commit hash when importing from GitHub.