Vault paths and POPE tags — bRRAIn Docs

Where an extension's files belong in the vault, and how to write POPE v1.1 tags and honest attribution in every content file.

Vault paths and POPE tags

There is no schema to declare. Files your extension writes become institutional memory that people search, open in Nexus and correct later, so their location and frontmatter do the work a schema would.

Where files belong

| Location | What goes there | How to write it | | --- | --- | --- | | extensions/<your-slug>/ | State, caches, logs, intermediate files, anything only your extension interprets | Vault().Write | | extensions/_<name>/ | Platform-reserved. Never read or write it; use the SDK call that owns it (for example OperatorEnv()) | none | | projects/<Name>/... and other shared folders | Content that belongs to the organization: published digests, uploaded attachments | Ingestion().Commit |

Examples:

| File | Path | | --- | --- | | Run state | extensions/hello-notes/state.json | | Run log | extensions/hello-notes/run.log | | Published digest for a project | projects/Pier-Refit/digests/2026-10-01-weekly-sync-digest.md | | Attachment a user uploaded | projects/Pier-Refit/inbox/<name> |

Path rules: vault-relative, no leading /, no leading dot, no ... Put dates first in file names so folders sort chronologically. Organizations grant people Read, Add, Update or Delete on named projects, and the platform enforces those grants on people's own requests; your extension enforces them for the users it acts for.

Frontmatter every content file carries

---
title: "Digest: Weekly Sync 2026-09-30"
date: 2026-10-01
author: "[AI] hello-notes"
tags: [pope/events/Weekly-Sync-2026-09-30, pope/persons/Ana-Ruiz, pope/projects/Pier-Refit]
---
  • author is honest: generated content is marked [AI] with your extension's name; content a person wrote carries that person.
  • tags carries POPE tags and is never omitted. A file with no entities has tags: [].

POPE tags

POPE (POPE-Tagging Standard v1.1) stands for Persons, Organizations, Places, Events: the named real-world entities a file touches. Tags drive tag search, the ontology graph and the Graph Editor.

| Rule | In code | | --- | --- | | Tags live in tags: and nowhere else | Never invent a pope: key with nested objects | | Prefixes pope/persons/, pope/orgs/, pope/places/, pope/events/, plus pope/projects/ and pope/products/ | Build tags from these prefixes | | Names are hyphenated title case: First-Last, Org-Name | Normalize spaces to hyphens | | One canonical spelling per entity | Look names up in the vault rather than retyping them | | Tag what the content references, not what it might relate to | Derive tags from your data | | A project is not an event; meetings and dated milestones are events | pope/projects/Pier-Refit, pope/events/Weekly-Sync-2026-09-30 | | Sorted by category, then alphabetically | Sort before writing |

Build tags in one tested function rather than inside a template string:

// popeName turns "pier refit" into "Pier-Refit".
func popeName(s string) string {
	parts := strings.Fields(s)
	for i, p := range parts {
		parts[i] = strings.ToUpper(p[:1]) + p[1:]
	}
	return strings.Join(parts, "-")
}

// popeTags builds sorted POPE v1.1 tags. The category prefixes sort
// alphabetically in the required order, so one sort suffices.
func popeTags(events, orgs, persons, places, projects []string) []string {
	tags := []string{}
	add := func(prefix string, names []string) {
		for _, n := range names {
			tags = append(tags, prefix+popeName(n))
		}
	}
	add("pope/events/", events)
	add("pope/orgs/", orgs)
	add("pope/persons/", persons)
	add("pope/places/", places)
	add("pope/projects/", projects)
	sort.Strings(tags)
	return tags
}

func WriteDigest(ctx context.Context, c *platformsdk.Client, project, meeting string, attendees []string, body string, now time.Time) (string, error) {
	path := fmt.Sprintf("extensions/hello-notes/digests/%s/%s-digest.md",
		popeName(project), now.Format("2006-01-02"))
	tags := popeTags([]string{meeting}, nil, attendees, nil, []string{project})
	doc := fmt.Sprintf(`---
title: "Digest: %s"
date: %s
author: "[AI] hello-notes"
tags: [%s]
---

%s
`, meeting, now.Format("2006-01-02"), strings.Join(tags, ", "), body)
	err := c.Vault().Write(ctx, platformsdk.VaultWriteRequest{Path: path, Content: []byte(doc)})
	return path, err
}

WriteDigest keeps the draft in the extension's namespace and returns the path it wrote, which you need for the audit event and the link you show the user. To publish the digest into the project folder, commit the same document with Ingestion().Commit (see Vault and ingestion).

Next