Open app
Moonborn — Developers

Persona-as-code (Git sync)

Connect a Git repository to your workspace; pull, push, or bidirectional sync personas as YAML + markdown with last-write-wins conflict detection.

Persona-as-code closes the loop between Moonborn's product surface and your engineering workflow: personas live in Git, every change shows up as a diff in a pull request, and the workspace stays synced.

Preview status. The domain and application layers are implemented (@moonborn/integration-git ships PullPersonasFromGitUseCase + PushPersonasToGitUseCase); the HTTP API and product onboarding UI are landing in the next release. Until then, this tutorial walks through the configuration shape — early-access customers can wire it up via direct integration.

1. Pick a sync mode

integrations.git.sync_direction controls the flow:

  • pull — Git is the canonical source. Workspace mirrors the repo.
  • push — Moonborn UI is canonical; Git is the audit trail.
  • bidirectional — both, with conflict resolution.

Most teams start with push for the first month — let the product UI generate the personas, watch the diffs land in Git, then switch to bidirectional once the team is editing personas in both places.

2. Configure the connection

await client.config.setItem({
  key: 'integrations.git.provider',
  value: 'github',
  scope: 'workspace',
  scopeId: 'ws_...',
});
await client.config.setItem({
  key: 'integrations.git.repo_url',
  value: 'https://github.com/your-org/persona-bible',
  scope: 'workspace',
  scopeId: 'ws_...',
});
await client.config.setItem({
  key: 'integrations.git.base_path',
  value: 'personas/',
  scope: 'workspace',
  scopeId: 'ws_...',
});
await client.config.setItem({
  key: 'integrations.git.sync_direction',
  value: 'push',
  scope: 'workspace',
  scopeId: 'ws_...',
});

Supported providers: github, gitlab, bitbucket. Each is independently tier-gated under integrations.git.providers.* (all Team+).

3. File layout

Each persona becomes a single file under the configured base path:

---
id: persona_01H...
name: Mert Aksoy
version: 4
layer: Soul
desire: build something the world can't ignore
fear: being seen as ordinary
wound: a parent who measured love in achievement
growth_arc: from approval-seeking to self-trusting
---
 
Mert spent his twenties in the Istanbul tech scene. His first
company sold to a Turkish telco — a quiet exit that he still
describes as "fine."

YAML frontmatter carries the structured layers (Soul, Self, Mask, Surface); the markdown body is the narrative grounding. The serializer emits this exact shape on push; the parser reads it back on pull.

4. Trigger the first sync

In the preview phase, sync runs are triggered via the application layer:

// (preview — direct integration only; HTTP endpoint coming)
import { PushPersonasToGitUseCase } from '@moonborn/integration-git/application';
await runPush({ workspaceId: 'ws_...' });

In production, this becomes:

# (planned)
curl -X POST https://api.moonborn.co/v1/integrations/git/push \
  -H "Authorization: Bearer $MOONBORN_API_KEY"

5. Conflict resolution

Bidirectional syncs use last-write-wins with explicit rejection. The strategy is set by integrations.git.conflict_resolution:

  • remote_wins — pulls overwrite local changes on conflict.
  • local_wins — pushes overwrite remote changes on conflict.
  • error — surface the conflict, halt the sync, return PersonaFileSyncConflictError with the affected persona IDs.

Conflicts are detected by Git blob SHA mismatch — no three-way merge, no CRDT.

6. Audit + commit messages

Commit messages follow engine.pipeline.audit.commit_message_template (default feat(persona): {{name}} v{{version}}). Each persona change produces one commit (with integrations.git.commit_strategy = per_change) or batched into one commit per sync run (with batched).

Git history becomes the de-facto change log. The Moonborn audit log records the sync operations themselves, not the persona deltas.

Tier

Team and up. Each provider toggle independent.

Next