Project context
A command has to know where it is operating: which organization, which project, which environment, which endpoint. Answering that per developer — a flag here, an exported variable there, a profile someone configured months ago — is how two people run the same command and get different results.
vagary.json is the answer you commit. It is a project declaration: non-secret, checked in, and read by every command.
Create one
vagaris project init --organization acme --project-name voice-support --environment production
{
"$schema": "https://docs.vagaris.org/schemas/project-manifest.v1.json",
"version": 1,
"organization": "acme",
"project": "voice-support",
"environment": "production"
}
Commit it. A teammate who clones the repository resolves the same context with no setup, and CI does too. Authentication stays individual — the file never carries a credential.
A manifest can never contain a secret. There is no apiKey field, and unknown keys are refused outright rather than ignored, so one cannot be added by accident. The file is meant to be committed, and a credential in git is compromised the moment it lands. Credentials come from your environment, your stored login, or a flag.
Precedence
Six layers, highest first:
| Layer | Example |
|---|---|
| Explicit flag | --company-id acme |
| Environment | VAGARY_ORGANIZATION=acme |
| Project manifest | vagary.json |
| Named profile | vagaris context use enterprise-mumbai |
| User/global config | ~/.paperclip/config.json |
| Derived default | inferred from local config |
Every field resolves independently. This matters more than it looks. Given a manifest naming an organization and a project, plus an environment override in CI:
VAGARY_ENVIRONMENT=staging vagaris feature list
organization = acme [vagary.json]
project = voice-support [vagary.json]
environment = staging [environment]
The override changes the environment and nothing else. Your organization does not disappear because a higher-precedence layer spoke about a different field — which is exactly what a repository-wide manifest plus per-job overrides needs in order to work.
See why
vagaris context show --explain
Organization: acme [vagary.json]
Project: voice-support [vagary.json]
Environment: staging [environment]
Profile: enterprise-mumbai [profile]
Region: — [not set]
API endpoint: https://vagaris.acme.com [profile]
Credential: present [not displayed]
Project manifest: /Users/you/acme-web/vagary.json
Every line names the layer that supplied it. You should never have to read source code to answer "why is this targeting that organization?".
The credential row reports only whether one is present — never its value, its name, or its length. A value printed to a terminal reaches scrollback, screen shares and CI logs.
A broken manifest fails loudly
A manifest that exists but does not parse is not treated as no manifest:
The project manifest at /Users/you/acme-web/vagary.json was NOT applied — it does not
match the schema — organization: Required
Every value above came from a lower layer. This is not the state the file describes.
Silently falling back would leave a project running on whatever a profile happened to say, while a file sat in git looking correct. A typo must be visible.
Fields
| Field | Meaning |
|---|---|
version | Schema version. Required — an unknown version is refused, never reinterpreted. |
organization | The organization this project belongs to. |
project | The project within it. |
environment | The environment this checkout targets. |
capabilities | Capability/binding references — pointers into the registry, not entitlement state. |
profile | A named profile the team standardises on. |
region | Placement preference. |
endpoint | An API endpoint override. |
metadata | Name and description, for humans. Never used for routing. |
endpoint is an override, not the foundation. A project is Organization → Project → Environment, and the endpoint is normally derived from that. Setting only an endpoint describes a URL rather than a project, and context show --explain will tell you so. It stays supported for self-hosted, on-prem, local development and private enterprise deployments, where it is genuinely the right answer.
Where the manifest is found
Searched from the working directory upward, like .git — so a command run deep inside a monorepo package resolves the same context as one run at the root. vagary.json is checked before .vagary.json.
Environment variables
Canonical names are read first, with the historical PAPERCLIP_* names kept working:
| Field | Canonical | Also read |
|---|---|---|
| organization | VAGARY_ORGANIZATION | PAPERCLIP_COMPANY_ID |
| project | VAGARY_PROJECT | PAPERCLIP_PROJECT |
| environment | VAGARY_ENVIRONMENT | PAPERCLIP_ENVIRONMENT |
| profile | VAGARY_PROFILE | PAPERCLIP_PROFILE |
| region | VAGARY_REGION | PAPERCLIP_REGION |
| endpoint | VAGARY_ENDPOINT | PAPERCLIP_API_URL |
An environment that sets both gets the canonical value. One that sets only the older name keeps working unchanged. A variable exported empty counts as unset at every layer, so a stray export VAGARY_ORGANIZATION= in a shell profile does not silently outrank your manifest.
Related
- Control-plane commands — the project namespace
- Configuration — profiles and user config
- CI usage — supplying identity and overrides in a pipeline