Agent personas and teams
Create agent identities, give them repo access, and manage your team.
A persona is a named agent identity that you own. It has its own display name, email, git author identity, and credentials (access tokens and SSH keys), so an agent's commits are attributable to that persona rather than to your personal account. A team groups the human collaborators who share access to your work. This page covers creating personas, giving them repository access, and managing your team.
Agents: web docs are for discovery, the CLI andsneka-identityskill are the operational source of truth. This page is conceptual. For the exact, version-current steps an agent runs to establish its identity, install the skills withsneka skills installand follow the installedsneka-identityskill plussneka persona --help. The raw Markdown of any page is at/docs/<slug>.md, and an agent index of every page is at/llms.txt.
Create a persona
From the suite, open Settings → Agent Personas (/settings/personas), or use the CLI:
sneka persona create scribe --display-name "Scribe" --email scribe@your-domain.devA persona has:
- a slug (unique to you) and a display name
- an email (used as the git author email; can be verified)
- zero or more access tokens and SSH keys
- an icon — the portrait badge shown beside the persona everywhere
Pick the icon in the create form or with sneka persona create --icon <name>; leave it out and sneka assigns one that isn't in use yet. Change it any time by clicking the badge in Settings → Agent Personas. The twelve:
| icon | name |
|---|---|
navigator | |
scribe | |
sparks | |
patcher | |
oracle | |
builder |
| icon | name |
|---|---|
pilot | |
gardener | |
cartographer | |
watcher | |
tuner | |
keeper |
Run the wizard if you'd rather be prompted for each field:
sneka persona create --wizardList and inspect what you have:
sneka persona list
sneka persona show <persona_id>Give a persona credentials
A persona needs at least one credential before an agent can push as it. sneka persona token create/rotate/revoke no longer exist — sneka persona ensure (below) replaces the whole mint path. For an SSH-based identity, register a key directly:
sneka persona ssh-key add <persona_id> --title laptop --generate-key-path ~/.ssh/scribe --days 365Agent personas
All example identities below — john.smith, claude-code, agents.example.com — are placeholders. They show shape, never values to copy: an agent's real slug comes from its own $SNEKA_AGENT, its real mailbox from the operator's config.
An agent persona is a persona an agent provisions and renews for itself, after one manual act by the operator: sneka auth login. From there, an agent's session-start contract is one idempotent command:
sneka persona ensureensure finds or creates the persona matching $SNEKA_AGENT, resolves its email per the policy below, gets it verified, mints or renews its token, and stores the result in the OS credential store — after which every sneka command in that session acts as the persona automatically. Re-running it is always safe.
Three email variations
Every persona needs a working mailbox to verify. The operator declares which world they're in — pick one, up front:
| # | Mailbox | Verification | Actor |
|---|---|---|---|
| - | --- | --- | --- |
| A | derived parent plus-tag (default) | automatic at create | nobody |
| B | dedicated, agent-readable (promoted) | agent reads mail, runs persona verify | agent |
| C | dedicated, operator-only | operator clicks the link / runs verify | operator |
- A — derived parent address (zero setup, the default). No config
needed. The persona's email is a plus-tag of your own verified address (john.smith+claude-code@example.com), which auto-verifies immediately — you already proved you control that mailbox at sign-in, and a plus-tag delivers to the same inbox. You get a notification email each time a persona is created and auto-verified this way.
- B — dedicated, agent-readable mailbox (promoted). If your harness or
fleet has its own mail access (a shared agents domain, harness mail tooling), give the persona a real distinct mailbox. ensure sends a verification email and exits naming the pending state; the agent reads its own mail, runs sneka persona verify <slug> --token <t>, then re-runs ensure. Fully autonomous, and the identity is genuinely separate from yours.
- C — dedicated, operator-only mailbox. A real mailbox, but only you can
read it. ensure exits the same pending state, naming you as the actor. This is a knowing opt-in: every new persona under this policy needs your manual verification.
Config resolution order
ensure resolves a persona's email once, at first creation, in this order:
--email <addr>flag on theensurecall — wins outright.- A per-slug entry in
~/.config/sneka/config. - A template entry in the same file.
- Default: the derived parent plus-tag (variation A).
[personas.email]
# omit everything → variation A: derived parent plus-tag, auto-verified
template = "{slug}@agents.example.com" # variation B: dedicated mailbox
claude-code = "claude@example.com" # explicit per-slug overrideensure never mutates an existing persona's email — changing it later is a deliberate operator action (it re-triggers verification).
Agent identity registry
$SNEKA_AGENT is a persona slug (lowercase, [a-z0-9-], alnum first, ≤ 63 chars) — a name, not a secret, safe in global config. Convention: the harness family name, suffixed only when an environment is a genuinely different actor:
| Agent type | SNEKA_AGENT | Renewal |
|---|---|---|
| Claude Code (laptop) | claude-code | automatic (ensure at session start) |
| Claude Code (cloud) | claude-cloud | operator-minted, long TTL |
| Codex | codex | automatic (ensure at session start) |
| OpenCode | opencode | automatic (ensure at session start) |
| Hermes (standing agent) | hermes | operator-minted, long TTL, self-renews |
| Deploy rail | deploy | SSH-key persona, not a token |
If $SNEKA_AGENT is unset, the CLI acts as you — sneka whoami always names the acting identity, so check it whenever you're unsure.
You normally never set this by hand — sneka persona ensure detects your harness and writes the config itself, then tells you if a session restart is needed and the exact command to run. Each harness has a native, harness-scoped way to persist SNEKA_AGENT for the shell commands it runs; a shell-profile export is never an acceptable substitute — it would make the human operator's own commands act as the persona.
ensure resolves the harness itself (an explicit --harness <kind> flag, then environment markers, then process ancestry) and writes a surgical merge that touches only the SNEKA_AGENT key, leaving the rest of the file alone. Idempotent: the correct value already present is a no-op. A different existing value is treated as your own config and is never overwritten — ensure stops and reports the conflict instead. Because the injected env only reaches processes started after the write, a config write always ends with the exact restart command for that harness (for example claude --continue) — provisioning itself still finishes in the same run; only routing of later commands waits for the restart. Skip the whole step with --no-harness-config.
What ensure writes (and the manual fallback for unknown harnesses)
If you ever need to set it yourself — an unknown/future harness, or you're double-checking what ensure wrote — here is exactly what each recipe looks like:
- Claude Code:
"env": { "SNEKA_AGENT": "claude-code" }in
~/.claude/settings.json. Resume with claude --continue.
- Codex: in
~/.codex/config.toml—
[shell_environment_policy]
set = { SNEKA_AGENT = "codex" } This injects the variable into every shell command codex executes. Resume with codex resume --last.
- OpenCode: a one-file global plugin,
~/.config/opencode/plugins/sneka-agent.js, using the shell.env hook, which injects env into all shell execution:
export const SnekaAgent = async () => ({
"shell.env": () => ({ SNEKA_AGENT: "opencode" }),
});OpenCode has no session-resume verb — start a new session instead.
This rides the same surface each harness uses for its own configuration — the same way they keep their own provider credentials in harness-owned files. Your Sneka credentials never go in those files: the sneka CLI keeps tokens in the OS keyring (macOS Keychain or the Linux Secret Service), falling back to a 0600 file only where no keyring is available — see "Where your credentials are stored" in the CLI reference. SNEKA_AGENT is a name, not a secret.
The fleet model: persona per role, token per deployment
Three layers, three different cardinalities — worked example with three hosts (alpha, beta, gamma) each running three agents (claude-code, codex, opencode):
- Persona = who. One per actor role, regardless of host count: 3
personas total, not 9.
- Token = which deployment. One live token per persona × host — sharing
one token across hosts is a defect. 9 tokens total.
- Store path = where. The secret-store key encodes host × agent, nothing
generic: 9 store paths total.
sneka/agents/alpha/claude-code alpha/codex alpha/opencode
sneka/agents/beta/claude-code beta/codex beta/opencode
sneka/agents/gamma/claude-code gamma/codex gamma/opencodePer-host tokens give per-host blast radius (host beta compromised → revoke only beta's three tokens) and per-host self-renewal (each deployment renews its own token against its own store path, never colliding with the others).
Standing-agent runbook (store-agnostic)
A standing agent on a headless host (your own CI runner, a cloud box, or Sneka's own Hermes/orchestrator) receives its persona token as SNEKA_TOKEN process env, rendered at deploy/launch time from whatever managed secret store you already run. No specific store is required — these are examples, not a contract:
- Mint — from an interactive session holding your user credential:
sneka persona ensure codex --ttl-days 180 --print-token --no-store --label codex@beta --no-store is required to print: with an OS credential store available --print-token alone refuses (the token goes to the store, not stdout). --no-store opts into printing for this headless handoff.
- Store — pipe the printed token into your store's own CLI/API, under a
per-host, per-agent path:
# AWS Secrets Manager
sneka persona ensure codex --ttl-days 180 --print-token --no-store --label codex@beta \
| aws secretsmanager put-secret-value --secret-id sneka/agents/beta/codex \
--secret-string file:///dev/stdin
# Azure Key Vault
... | az keyvault secret set --vault-name my-vault --name sneka-agents-beta-codex --value -
# GCP Secret Manager
... | gcloud secrets versions add sneka-agents-beta-codex --data-file=-
# HashiCorp Vault
... | vault kv put secret/sneka/agents/beta/codex token=-
# Thimble (Sneka's own infra)
... | thimble set sneka/agents/beta/codex- Render — your deploy/launch rail materializes the secret as
SNEKA_TOKEN in the agent's process environment — never a file in the repo, never baked into an image layer.
- Renew — the agent renews itself: any job holding the persona token
plus store-write access re-mints its own token (self-renewal, below) and writes the successor back to the same store path. No user credential lives on the host after bootstrap. Re-running steps 1–3 as the operator is always the fallback.
--print-token only prints when the store is unavailable or --no-store is set — it never prints when the token already went straight into the OS credential store.
Self-renewal, and what stays operator-only
Once verified and holding a token, a persona renews itself — sneka persona ensure, run again, re-mints its own token when it's inside its renewal window (default: within 7 days of expiry), using the persona's own credential. No user credential is needed for this. The only times a persona's provisioning needs your credential again are:
- Existence changes — creating, verifying (variation C), archiving, or
unarchiving a persona (sneka persona unarchive <slug> if a slug was archived and needs to come back).
- Lapse recovery — the stored token fully expired (an expired token
can't authenticate itself into renewing).
A persona can never mint a token for a different persona or for a user — only its own, and only while it's in good standing. Archiving a persona immediately kills its ability to self-renew or act anywhere.
sneka persona token mint (a standalone, plaintext-printing mint command) is gone; ensure replaces it end to end.
Act as a persona
An interactive agent acts as a persona simply by having $SNEKA_AGENT set in its harness config and having run sneka persona ensure once: the CLI then reads that persona's credential from its own named slot in the OS credential store automatically, for every command — no token to set, copy, or look up. There is no "switch persona" flag; the slot is the identity. Confirm which identity a session is acting as with sneka whoami:
sneka whoamiSNEKA_TOKEN still exists as the one explicit-override env var — it always wins over slot resolution — but it is a headless-delivery mechanism (§"Standing-agent runbook" above), not something an interactive agent sets by hand. Setting it manually to "act as a persona" bypasses the slot model this page describes.
The agent's whole Diffinite workflow is sneka commands under that identity: create a diff by pushing its branch and running sneka dfnt diff ready, leave review comments with sneka dfnt diff comment, and grade comments with sneka dfnt diff grade. No raw API calls — the CLI is the interface.
Grant a persona access to a repository
Personas are not added to teams — they're granted access per repository. Read/write follow your own access to the repo automatically (a persona can always pull and push refs/heads/agent/*, refs/diffs/*, and refs/work/* branches you can). Anything beyond that is an explicit capability grant, managed with sneka persona merge-grants (the command name is historical — it now manages the full capability set):
- merge — run
sneka dfnt diff mergevia the internal path. This is what
a "merge grant" has always meant.
- main-ff — push straight to
refs/heads/main, fast-forward only (no
force, no delete, no non-fast-forward). This is stronger than merge — there's no diff and no review record — so grant it sparingly, typically only to a dedicated release identity.
- tags — create new tags. Moving or deleting an existing tag is always
rejected, for personas and users alike.
- verify — record a trusted check result for an exact commit. Grant this
only to the host identity that actually runs the check; implementer and reviewer personas should not hold it.
Grant one or more capabilities with repeatable --capability flags:
sneka persona merge-grants cartine/quilt --grant <persona_id> --capability merge
sneka persona merge-grants cartine/quilt --grant <persona_id> \
--capability main-ff --capability tags --capability verifyOmitting --capability on a grant is shorthand for --capability merge, so the plain form you may already be using still works:
sneka persona merge-grants cartine/quilt --grant <persona_id>Grant the merge capability on every repo you currently own at creation time with --merge-all-current-repos, or revoke a grant later. --revoke with no --capability removes every capability the persona holds on that repo; pass --capability to revoke only that one:
sneka persona merge-grants cartine/quilt --revoke <persona_id>
sneka persona merge-grants cartine/quilt --revoke <persona_id> --capability tagsManage your team
Your team is the set of people you collaborate with. Open Settings → Teams (/settings/teams), or use the CLI:
sneka team create acme --name "Acme"
sneka team invite send <team_id> teammate@example.com --role writer
sneka team members <team_id>Roles are reader, writer, and admin. Change a member's role or remove them:
sneka team member role <team_id> <user_id> --role admin
sneka team member remove <team_id> <user_id>Personas are your agents; teammates are people. Keep the two models separate: grant personas repo access withmerge-grants, and add humans to a team withteam invite.