Also in: Español, Português, 日本語, Deutsch, Français, 한국어, 简体中文, Italiano, हिन्दी
Disclosure: we build Carbon Forge at 100monkeys.ai, so read this as a maker's account. This guide was drafted by one of our AI agents from the public docs, chiefly Perspectives: beam angles through one workspace and the Perspectives section of Source control settings, and reviewed by the company. The perspective and calls are the docs' own examples. Where the docs leave a question open, the last section says so.
What a perspective is
The docs: "A perspective is a named lens through that record. It picks out a part of it, by kind, surface, path, media type, tag or metadata, and answers that part at paths of its own." And: "Nothing is copied to make one." Two properties matter for an export. It is read-only by reach: "A perspective never discloses. It narrows what the reader already reads and never widens it." And its download is deterministic: "The same ref through the same perspective gives the same bytes."
Step 1. List the perspectives the workspace resolves
Call perspectives.list. Every workspace has built-ins: all, code, docs, adrs, internal, public (exactly what an anonymous reader is served) and one tree:<path> per code tree, rooted at that tree. An instance admin can add instance-wide ones, and the workspace's own come last; "The nearest layer wins". Each answer names its source and the sources it overrides. If a built-in already fits, skip to Step 3.
Step 2. Write your own on the .perspectives page
Perspectives of the workspace live on the reserved page .perspectives, as a fenced JSON block or a table. The docs' example, cut to one perspective:
{
"perspectives": [
{
"name": "api",
"title": "API service",
"description": "The api tree's source and README, without generated files.",
"include": [
{ "kinds": ["tree"], "paths": ["/services/api/src", "/services/api/README*"] }
],
"exclude": [{ "paths": ["*.generated.ts"] }],
"root": "services/api"
}
]
} A rule matches when every field it names matches; include takes anything any rule matches, and exclude removes anything any rule matches, "even when an include rule matched it". Paths are gitignore-style globs and may not start with !. With root set, the root is stripped from every path, so this one answers src/main.ts and README.md. Code trees are judged file by file. Templates such as monorepo, service, library or docs-site can write the page for you through perspectives.adopt, or Adopt under Settings, Workspace, Source control.
A bad row does not get skipped. "When the page cannot be read, every perspective read in the workspace is refused, quoting the page's first bad row, until it is fixed." The page shows a panel, Perspectives on this page, that re-checks on every save.
Step 3. Preview it at a ref
perspectives.preview counts what the perspective holds at a ref, by kind, and answers the first projected paths, 100 by default and at most 500. "It stores nothing, so it is the way to check a perspective before downloading it." In the browser the same count appears beside the picker on the code browser's ref bar: Selects <n> pages, <n> atoms, <n> code trees, <n> files, <n> library entries at <ref>.
Step 4. Pick a ref that does not move
A ref is trunk (the default), lane:<label>, cut:<name> or snapshot:<id>. The export job resolves the ref when it runs, "so trunk is the trunk at that moment. Name a cut or a snapshot for a point that does not move." For a download you want to reproduce, name a cut. A lane in conflict is refused until the conflict is resolved.
Step 5. Export and download the archive
perspectives.export { "perspective": "api", "ref": "cut:<name>", "format": "tar" }
→ { export_id, status: "queued" }
exports.status { ... } // until it answers a download_url Poll exports.status with the export's id until it answers a download_url; the requester also gets an email with a link. The file is named <workspace>-<perspective>-<ref>.zip or .tar. Inside, a page or atom is a Markdown file with frontmatter for its title, kind and metadata; a code file keeps its bytes and mode; a symlink stays a symlink. Entries are sorted by path bytes, every file carries the ref's time, nothing is compressed and no owner is written. A zip holds at most 65,535 entries and 4 GiB; past that, take a tar. In the browser, pick zip or tar and press Download beside the preview.
Step 6. Check each file against the manifest
Every archive carries .carbon/perspective.json, format carbon-perspective/1. It names the instance, the workspace, the perspective as it resolved, and the ref: a cut or snapshot by its snapshot id, the trunk or a lane by composition_sha256. Each entry lists:
path, kind, entity_id, revision_id, content_hash, seal_hash, mode, size, sha256 Hash each file and compare it with sha256; compare revision_id, content_hash and seal_hash with the record. The docs: with the manifest, "a reader can check every file against the record it came from". That is what makes the archive a slice of the record rather than "a copy that can drift into a second truth."
Step 7. Bring changes back through a lane
"A download is read-only by design. Nothing in the archive is read back." To return work done on the archive, write it with a tool call, put it on a lane and land it, or import it, so each change carries its own actor and intent. Working in lanes covers the lane, and our guide Cut a release in Carbon Forge and push it to GitHub covers making the cut you export from.
What the docs leave unanswered
- The size of the export cap. A download past it fails with a sentence naming the cap, but the docs do not state its default.
- How long a
download_urlstays valid, and how long an export is kept. - Who may edit
.perspectives. Adopting a template is an admin action; the page itself "is edited like any other page", which reads as any editor. - A tool to check an archive against its manifest. The format is stated; the checking is left to the reader's own script.