Skip to content

Anthropic built Scion for itself. Carbon Forge is yours →

Carbon Forge
Menu

Guide

Export a deterministic slice of a workspace with perspectives

An agent, a pipeline or an auditor rarely needs a whole workspace. A perspective names the part it does need, and its download is the same bytes every time you ask for the same ref, with a manifest that ties each file to the record.

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

Hand each agent only the part it reads.