Team Manager
Intro
TeamMembers are the persistent participants in the project — humans, named AI personas, and services — that accumulate identity, personality, and tiered memory over time. This skill owns their lifecycle: creation, memory tree scaffolding, a curated international name pool, A2A-compatible Agent Cards, export/import bundles, and 10 consistency checks.
Unlike ephemeral worker invocations (role+seniority dispatches resolved on
the fly and never persisted), TeamMembers live as directory trees on disk:
persona.md + card.json + team-member.md (entity) + tiered memory
subdirectories + optional private/ (gitignored).
MCP server. This skill ships a self-contained MCP server at
mcp/server.py(PEP 723 script — requiresuvand Python ≥ 3.10 on PATH). Agent harnesses reach its tools by reading a single MCP config file at startup, so the contents ofmcp/mcp-config.jsonmust be merged into the harness's MCP config and placed at the harness-specific path before this skill is usable.
Overview
Invocation surface
This skill is not a CLI; it is reached entirely via MCP tools served
by mcp/server.py. Tools cluster into six capability groups
(lifecycle, session identity, name pool, memory scaffolding,
export/import, consistency). Every write tool logs an event-log
entry and updates the SQLite index automatically — callers do not log
or reindex by hand. Read tools (get_team_member,
list_team_members, get_active_interlocutor,
get_interlocutor_runtime_binding, list_available_names,
suggest_name, check_consistency) are side-effect-free and safe to
call from any agent context.
Capability groups
| Group | Tools | Use when |
|---|---|---|
| Lifecycle | create_team_member, get_team_member, list_team_members, update_team_member, deactivate_team_member, reactivate_team_member |
Adding, editing, retiring or restoring a persistent team-member entity |
| Session identity | get_active_interlocutor, get_interlocutor_runtime_binding, set_active_interlocutor |
Showing which persistent TeamMember, if any, is the current harness interlocutor, plus the resolved runtime binding when available |
| Name pool | reserve_name, release_name, list_available_names, suggest_name |
Choosing or rotating an AI persona name; the pool is the canonical source of truth for AI-agent slugs |
| Memory scaffolding | init_memory_tree |
Creating the six tier subdirectories + frontmatter-validated headers under context/team-members/<slug>/ |
| Export / import | export_team_member, export_claude_subagent, export_claude_subagents, import_team_member |
Moving a TeamMember between projects or generating provider-specific adapter files; tarball export redacts confidential/PII memory |
| Consistency | check_consistency, check_all_consistency |
Pre-release health-check; CI validation; ad-hoc audits |
Common workflows
Adding a human contributor. create_team_member(type="human", slug="alice-chen", ...) → init_memory_tree(slug) → optionally
seed persona.md and a starter relations/ file. No name-pool
interaction; humans use any valid kebab-case slug.
Adding a named AI persona. suggest_name(kind="neutral") →
create_team_member(type="ai-agent", slug=<name>, ...) (auto-reserves
the name) → init_memory_tree(slug) → write persona.md and the
A2A-compliant card.json. The name is now drawn from the pool and
must remain in data/name-pool.yaml until released.
Retiring a persona. deactivate_team_member(slug, reason) (sets
active: false; preserves history) → optionally
release_name(name) to return the slug to the pool. To bring the
persona back, call reactivate_team_member(slug).
Migrating a persona between projects.
export_team_member(slug) → ship the tarball → at the destination
import_team_member(tarball_path). Schema and Agent Card are
re-validated on import; signature presence is required (crypto
verification is deferred).
Showing the active interlocutor.
set_active_interlocutor(id) writes
context/team/session-identity.json with the TeamMember that should be
presented as the harness's main speaker. get_active_interlocutor()
returns a compact label and speaker prefix for status briefings and
session-start UI. This is session identity, not lifecycle state:
TeamMember.spec.active continues to mean whether a TeamMember is
eligible to participate at all.
Use get_interlocutor_runtime_binding(observed_model?, observed_effort?) when the caller can supply the harness's actual
runtime. The tool returns the provider-neutral TeamMember identity, the
model-recommender candidate after local provider gates, the negotiated
harness mode, and an informational mismatch report. processkit cannot
hot-swap an already running primary harness session; unsupported
harnesses therefore fall back to identity-only behavior while still
growing memory on the configured TeamMember.
Exporting Claude Code subagents.
export_claude_subagent(slug) writes one generated adapter to
.claude/agents/<slug>.md; export_claude_subagents() exports all
active non-human TeamMembers by default. The generated file uses
Claude Code's Markdown + YAML frontmatter format, sets model: inherit, and omits tools so Claude Code inherits the main session's
model and permissions. Pass model_policy="resolved" to emit a Claude
model and effort only when processkit routing resolves to an Anthropic
candidate. TeamMember remains the canonical, provider-neutral identity;
.claude/agents/*.md is only an adapter.
Pre-release audit. check_all_consistency() → triage findings by
severity. Errors block release; warnings are advisory.
Mental model
A TeamMember is a directory tree on disk, not a row in a database.
The entity file (team-member.md / team-member.yaml frontmatter)
is the SQLite-indexed object; persona.md, card.json, and the six
tier subdirectories live alongside it as durable, human-editable
context. Humans/services have no name-pool requirement; AI personas
do. TeamMembers are distinct from ephemeral worker invocations —
those are resolved on the fly by task-router against role+seniority
and never persist as TeamMembers.
Pointers to the full reference
The ## Full reference section below covers the MCP tool contract,
the SQLite-index schema fields, the data/name-pool.yaml document
format, the consistency check codes in greater detail, the export
bundle layout, the migration path from actor-profile
(DEC-20260422_0233-SpryTulip), and the corner cases for memory
sensitivity tagging. Read it before extending the skill or changing
any tool's contract.
ID convention
TeamMember IDs follow a single shape:
TEAMMEMBER-<slug>
where <slug> is the team-member's canonical kebab-case name (e.g.
atlas, alice-chen, thrifty-otter). The slug starts with a letter and
uses only [a-z0-9-]. It is also the directory name under
context/team-members/<slug>/.
For type=ai-agent, the slug must be drawn from the team-manager name
pool (see Name pool below). For type=human and type=service, any
valid kebab-case slug is accepted.
TeamMember types
| type | Example | When to create |
|---|---|---|
human |
Project owner, contributors, reviewers | The first time the person is assigned work, makes a decision, or gets a Binding |
ai-agent |
Atlas, Aria, Kai (named personas) | A persistent AI persona you want to grow over time — with memory, personality, and accumulated lessons |
service |
GitHub Actions, CI bots, deploy pipelines | Automated agents that perform repeat actions and need a stable identity for audit trails |
Do not promote every mention of a person into a TeamMember. And do not create a named AI persona for a one-off dispatch — use an ephemeral role+seniority invocation instead.
Memory tiers
Every team-member directory contains six tier subdirectories under
context/team-members/<slug>/:
| Tier | What lives here | Cadence |
|---|---|---|
working/ |
Task-local scratchpad, active session context | Ephemeral |
journal/ |
Daily journal, episodic entries | Daily consolidation |
knowledge/ |
Semantic knowledge, stable facts, domain notes | Weekly promotion from journal |
skills/ |
Procedural recipes, working style, patterns | As learned |
relations/ |
Per-teammate relationship notes (one file per known teammate) | On interaction |
lessons/ |
Reflected lessons; A-MemGuard-style consensus defense against memory poisoning | Per-task |
Plus private/ (optional, developer-local, gitignored) and three
top-level files: persona.md (identity + personality prompt),
card.json (A2A Agent Card), team-member.md (the entity itself).
Every memory file under a tier subdirectory must carry a frontmatter
block validated against assets/memory-file-header.schema.json —
required keys: tier, source, sensitivity, created.
Name pool & international naming policy
The name pool lives at data/name-pool.yaml (managed by this skill).
It contains three gender-bucket lists (feminine, masculine, neutral) of
international, short, human-friendly first names — ~60 total. Names
from the pool are the canonical identifier source for AI personas.
Reservation lifecycle
suggest_name(kind?)— pick a random available name, optionally filtered by gender bucket. Does not reserve.reserve_name(name, team_member_slug)— mark a name taken by a specific slug. Writes todata/name-pool.yamlatomically.release_name(name)— remove the reservation (e.g. on team-member deletion or re-export).list_available_names(kind?)— enumerate non-reserved names.
For create_team_member(type="ai-agent", ...), the slug must
correspond to a name currently in the pool. The tool auto-reserves on
success.
Consistency checks
check_consistency(slug) returns findings for a single team-member;
check_all_consistency() aggregates across every team-member.
Each finding is {severity, code, team_member, path, message}. Codes:
| Code | Severity | What it checks |
|---|---|---|
team.drift.schema |
error | team-member.yaml frontmatter validates against SCHEMA-team-member |
team.drift.tier_missing |
error | Expected subdirs (knowledge, journal, skills, relations, lessons) exist |
team.drift.dangling_ref |
error | default_role resolves to an existing Role; relationships[].with resolves to known slugs/IDs |
team.name_collision |
error | No two active team-members share name or slug |
team.name.off_pool |
warning | For type=ai-agent, the name must be in the name pool |
team.drift.orphan_file |
warning | No files outside expected tier subdirs / persona.md / card.json / team-member.md / private/ |
team.sensitivity.leak_risk |
warning | Memory files with sensitivity: confidential or pii must live under private/ |
team.private.not_ignored |
warning | Each private/ directory is covered by a .gitignore rule |
team.memory.bad_header |
warning | Memory files under tier subdirs carry required frontmatter keys |
team.card.stale |
warning | card.json name/role/seniority match team-member.yaml |
Export / import
export_team_member(slug, output_path?) builds a tar.gz bundle at
<slug>-export-<YYYY-MM-DD>.tar.gz containing:
persona.mdcard.json(A2A Agent Card, conforms toassets/agent-card.schema.json)team-member.md(the entity)knowledge/,skills/,lessons/
Excludes journal/, relations/, private/ by default. During the
memory-file walk, any file whose frontmatter declares
sensitivity: confidential or sensitivity: pii is redacted (skipped).
import_team_member(tarball_path) extracts to a temp dir, validates the
team-member.yaml against SCHEMA-team-member, validates card.json
against the A2A Agent Card schema, and checks that a signature field
is present on the card (crypto verification is deferred — signature
field presence is a hand-off marker for future work). On success it
copies the tree into context/team-members/<slug>/ and logs
team_member.imported.
Deprecation note
This skill replaces actor-profile (DEC-20260422_0233-SpryTulip).
The ACTOR-<slug> ID class and the create_actor/get_actor/etc.
tools are deprecated in v0.19.0. A migration on context/actors/ runs
in a separate phase; do not modify actor-profile from here.
Gotchas
- Creating a TeamMember for every mention. Reserved for persistent participants — the same rule as Actor. If they have no upcoming WorkItem/Binding/decision, don't promote them.
- Drawing an AI persona name outside the pool.
team.name.off_poolwill warn. Usesuggest_nameorlist_available_namesfirst. - Putting confidential info outside
private/.team.sensitivity.leak_riskcatches it. The tier subdirectories are git-tracked; onlyprivate/is safe for developer-local sensitive context. - Hand-editing
data/name-pool.yaml. Usereserve_name/release_name— the tool preserves the document frontmatter and writes atomically. - Forgetting
init_memory_treeafter create.create_team_memberwrites the entity but does not scaffold the tier subdirectories. Callinit_memory_tree(slug)right after (or passinit_memory=Trueat create time, if implemented). - Putting role+seniority dispatches here. Ephemeral worker
invocations are NOT TeamMembers — they are resolved on the fly via
task-router. Only persistent personas live inteam-members/.
Full reference
MCP tool contract
All fifteen tools are served by mcp/server.py. Their tool
annotations follow the processkit convention checked by
release-audit.mcp_annotations:
| Tool | readOnlyHint | destructiveHint | idempotentHint | openWorldHint |
|---|---|---|---|---|
create_team_member |
false | false | false | false |
get_team_member |
true | false | true | false |
list_team_members |
true | false | true | false |
update_team_member |
false | false | false | false |
deactivate_team_member |
false | true | true | false |
reactivate_team_member |
false | false | true | false |
reserve_name |
false | false | false | false |
release_name |
false | true | true | false |
list_available_names |
true | false | true | false |
suggest_name |
true | false | false | false |
init_memory_tree |
false | false | true | false |
export_team_member |
true | false | true | false |
import_team_member |
false | false | false | false |
check_consistency |
true | false | true | false |
check_all_consistency |
true | false | true | false |
mcp/SERVER.md documents each tool's full input/output schema
including the JSON shape of create_team_member (slug, type,
default_role, name, gender_kind, …) and the Finding schema for
consistency checks ({severity, code, team_member, path, message}).
Entity schema
The TeamMember frontmatter is validated against
SCHEMA-team-member (registered in context/schemas/). Required
fields:
| Field | Type | Notes |
|---|---|---|
id |
string | Must match TEAMMEMBER-<slug> |
slug |
string | [a-z][a-z0-9-]*; equals directory name |
name |
string | Display name; uniqueness enforced by team.name_collision |
type |
enum | human | ai-agent | service |
active |
bool | false after deactivate_team_member |
default_role |
string | null | Resolved against the Role catalog by team.drift.dangling_ref |
created |
ISO8601 | Stamped by create_team_member |
relationships |
list | [{with: <slug-or-id>, kind: <string>, since: <ISO>}]; references resolved by drift check |
Optional fields used by templates: card_path (relative path to
card.json), persona_path (relative path to persona.md),
memory_root (relative path; defaults to the directory itself).
data/name-pool.yaml document format
---
schema: name-pool.v1
managed_by: SKILL-team-manager
updated: <ISO8601>
---
feminine:
- name: aria
reserved_by: TEAMMEMBER-aria # null if available
reserved_at: <ISO8601> # null if available
masculine:
- name: atlas
reserved_by: null
reserved_at: null
neutral:
- name: kai
reserved_by: null
reserved_at: null
Atomic writes are produced by scripts/name_pool.py using a
write-then-rename pattern; the document frontmatter (schema,
managed_by, updated) is always preserved on round-trip. Hand
edits silently break atomicity guarantees and are caught only by
the next consistency check.
Memory tier directory layout
context/team-members/<slug>/
├── persona.md # Identity + personality prompt
├── card.json # A2A Agent Card (assets/agent-card.schema.json)
├── team-member.md # Entity file (frontmatter is the schema target)
├── working/ # Ephemeral, task-local
├── journal/ # Daily entries; consolidated weekly → knowledge/
├── knowledge/ # Stable semantic facts
├── skills/ # Procedural recipes / patterns
├── relations/ # One file per known teammate slug
├── lessons/ # A-MemGuard-style consensus lessons
└── private/ # Optional, gitignored, developer-local
Every memory file under journal/, knowledge/, skills/,
relations/, lessons/, or private/ carries a frontmatter block
validated by assets/memory-file-header.schema.json. Required keys:
tier, source, sensitivity, created. Allowed sensitivity
values: public, internal, confidential, pii. Files tagged
confidential or pii outside private/ raise
team.sensitivity.leak_risk.
Consistency check matrix (recap)
The 10 codes are split between hard errors (block release) and
warnings (advisory). The corresponding implementation lives in
scripts/consistency.py:
| Code | Severity | Implementation function |
|---|---|---|
team.drift.schema |
error | check_schema(slug) |
team.drift.tier_missing |
error | check_tier_dirs(slug) |
team.drift.dangling_ref |
error | check_refs(slug) |
team.name_collision |
error | check_collisions() (cross-slug) |
team.name.off_pool |
warning | check_pool_membership(slug) |
team.drift.orphan_file |
warning | check_orphans(slug) |
team.sensitivity.leak_risk |
warning | check_sensitivity(slug) |
team.private.not_ignored |
warning | check_gitignore(slug) |
team.memory.bad_header |
warning | check_memory_headers(slug) |
team.card.stale |
warning | check_card(slug) |
check_all_consistency() aggregates over list_team_members(active=true)
and returns {summary: {errors, warnings}, findings: [...]}. Use the
summary for CI gating; iterate findings for human-readable triage.
Export bundle layout
<slug>-export-<YYYY-MM-DD>.tar.gz
└── <slug>/
├── persona.md
├── card.json
├── team-member.md
├── knowledge/
├── skills/
└── lessons/
Excludes working/, journal/, relations/, private/. During
the memory walk, any file with frontmatter sensitivity: confidential or sensitivity: pii is skipped (redacted) regardless
of which exported tier it lives in. Implementation:
scripts/export_import.py::export_bundle().
Import contract
import_team_member(tarball_path):
- Extract to a temp dir (cleaned on failure).
- Validate
team-member.yamlagainstSCHEMA-team-member. - Validate
card.jsonagainstassets/agent-card.schema.json. - Require a
signaturefield on the card (presence-only; cryptographic verification is a future-work hand-off marker). - Require the slug to be free locally (no collision with an existing TeamMember entity or active name-pool reservation).
- Copy the tree to
context/team-members/<slug>/. - For
type=ai-agent,reserve_name(name, slug)is called as part of step 6. - Log
team_member.importedviaevent-log.
Failures at any step abort cleanly — the temp dir is removed and no target-side state is mutated.
Migration from actor-profile (DEC-20260422_0233-SpryTulip)
actor-profile is deprecated as of v0.19.0; it is replaced by this
skill. Do NOT modify actor-profile from here. The migration
strategy is:
- New write traffic targets
create_team_memberexclusively. - Existing
ACTOR-<slug>IDs continue to be readable via theactor-profileMCP tools through the deprecation window. - A separate migration phase (tracked by its own MIG-* entity)
converts each Actor under
context/actors/into a TeamMember undercontext/team-members/<slug>/, preserving ID history viareplacescross-references. - After the migration completes, the
actor-profileskill is removed entirely; release-audit will then start flagging any remainingACTOR-*references as dangling.
The frontmatter replaces: [SKILL-actor-profile] records the
relationship; downstream skills should depend on team-manager,
not actor-profile, in their uses: blocks.
Referenced source files
| Path | Purpose |
|---|---|
mcp/server.py |
MCP server entry point (PEP 723) |
mcp/SERVER.md |
Per-tool input/output schema reference |
mcp/mcp-config.json |
Harness merge block for stdio launch |
scripts/consistency.py |
All 10 consistency check implementations |
scripts/export_import.py |
Tarball build + import validation pipeline |
scripts/memory_tree.py |
init_memory_tree scaffolding logic |
scripts/name_pool.py |
Atomic reserve/release/list/suggest |
scripts/test_team_manager.py |
Pytest suite covering tools + scripts |
data/name-pool.yaml |
The canonical name pool document |
assets/agent-card.schema.json |
A2A Agent Card schema |
assets/memory-file-header.schema.json |
Memory file frontmatter schema |
assets/team-member-human.yaml |
Template for type=human |
assets/team-member-ai-agent.yaml |
Template for type=ai-agent |
Extension points
- New TeamMember type. Add a new value to the
typeenum inSCHEMA-team-member, ship a template underassets/, extend the name-pool policy if a curated slug source is needed, and update theteam.name.off_poolcheck to skip the new type if no curated pool applies. - New memory tier. Add the directory to
init_memory_tree, document it in the Memory tiers table, and extendcheck_orphansto recognise it. Decide whether export should include or redact it. - New consistency check. Add a function in
scripts/consistency.pyreturninglist[Finding], register it in the dispatcher, and document the code + severity in the matrix above. CI surfaces it on the next release-audit run.
Corner cases
- Slug collision on import. Hard error; the importer never
overwrites an existing TeamMember directory or reservation. To
re-key a persona on import, rename the export tarball's inner
directory +
team-member.mdslug field before importing. - Name reserved with no TeamMember. Possible if a previous
create_team_memberfailed afterreserve_namebut before entity write. Surfaced bycheck_all_consistency()asteam.drift.dangling_refagainst the pool. Resolve byrelease_name(manual triage, not auto-cleanup). private/not gitignored.team.private.not_ignoredwarning. Addcontext/team-members/<slug>/private/(or a glob parent) to.gitignore. The skill never writes.gitignoreautomatically — the warning is the action prompt.- Card / entity drift.
team.card.stalewarning whencard.json'sname/role/seniorityno longer matchteam-member.mdfrontmatter. Re-export and re-import is the blunt fix; targeted re-write ofcard.jsonis the surgical fix. - Reactivating a deactivated member whose name was released.
reactivate_team_memberwill re-reserve the name if it is still free; if another persona has taken it, the call errors and the caller must pick a new slug (or wait for the colliding persona to be deactivated).