Files
PaperClipAI/docs/guides/board-operator/importing-and-exporting.md
Devin Foley 916c13501f Replace host-to-host Cloud Sync with full-fidelity company Import/Export (#10507)
## 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
2026-07-30 11:37:00 -07:00

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-id to 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., ceo becomes ceo-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.