omp packages

@grrcodebar/profilemanager0.1.0

Isolated pi agent profiles: each profile is its own agent dir, opened as a herdr tab with the session loaded

grrcodebar Published 9 days ago MIT ~375 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

profilemanager

Isolated pi agent profiles + programmatic collaboration between them, without burning model tokens.

Perfiles de agentes pi aislados + colaboración programática entre ellos, sin quemar tokens del modelo.

English

What it is

Each profile is a folder that works as a pi agent dir (PI_CODING_AGENT_DIR points at it on open): settings, skills, MCPs, secrets, system prompt and knowledge all come from the profile; your global config is untouched. Two sessions on different profiles load different things, even at the same time.

Every profile also loads this package, which gives all of them the same tools:

  • profile_task — shared task board across profiles. Delegate with an id, the other profile closes it with done (the ok). Nobody blocks waiting.
  • profile_skill — the agent saves what it learns as skills in its own profile library.
  • /profile — list, show, open profiles, approve skills.
  • A file watcher that queues task news into the transcript (never forces a model turn) plus a zero-token UI toast.
  • A perfil: <name> footer, so you always know which profile a session runs.

Design rule: everything coordination-related is programmatic (files + code), so it costs ~zero tokens until actual work happens. herdr is only session lifecycle (open/reuse tabs); pi core has no subagents, so without herdr the board itself is the fallback — tasks persist and the next session picks them up.

Profile structure

profiles/writer/
├── PROFILE.md                  # docs: name, what it does, skills, tools, usage
├── settings.json               # pi config of the profile (flat defaultTools, packages)
├── SYSTEM.md                   # system prompt of the profile
├── mcp.json                    # profile MCPs + secrets (env), nothing global
├── knowledge/                  # knowledge files of the profile
├── agents/writer.md            # herdr-compatible definition (frontmatter + prompt)
└── skills/<skill>/SKILL.md     # isolated skills: only this profile's

CLI usage

profilemanager create writer --desc "Writes docs"
profilemanager list
profilemanager show writer
profilemanager open writer            # herdr tab with pi + profile; without herdr prints the manual equivalent
profilemanager open writer --dir ./my-profiles

Default vault: M:/memory (override with --dir or PROFILEMANAGER_DIR).

Default config (markdown)

Your defaults for new profiles live in a hand-editable .md: ~/.pi/agent/profilemanager.md (override: PROFILEMANAGER_CONFIG). profilemanager defaults show creates it from the template if missing.

profilemanager defaults show                                      # applies the template if missing
profilemanager defaults edit                                       # prints the path to edit it
profilemanager defaults package add pi-herdr                       # global name, npm:, git: or path
profilemanager defaults package remove pi-herdr
profilemanager defaults set defaultTools "read, grep"
profilemanager defaults set learningNudgeInterval 30
profilemanager defaults set systemPromptAppend "Answer in English."

Applied on create (--with/--all-global are added to the default packages). Comments and custom sections are preserved: the CLI only rewrites the section it touches. This package is always seeded too, so the learning loop and the task board exist in every profile.

Packages per profile

packages are part of the agent dir, so a profile session loads only what its settings.json declares. create seeds this package (absolute path).

profilemanager package global                       # what is installed globally
profilemanager create writer --with pi-herdr,pi-lens   # pick by name (short name is enough if unique)
profilemanager create full --all-global              # all globals

profilemanager package add writer pi-herdr      # bare name -> npm:pi-herdr
profilemanager package add writer npm:@scope/x
profilemanager package add writer ./my-package  # local path -> absolute
profilemanager package list writer
profilemanager package remove writer pi-herdr

The npm dir (~/.pi/agent/npm) is shared: a package already installed with pi install <source> resolves from the profile without reinstalling.

Without an active profile none of this applies: if there is no PI_CODING_AGENT_DIR, pi uses your global agent dir and loads your global packages as always.

Editing a profile (everything stays isolated in its folder)

profilemanager skill add writer ./my-skills/notes [--as notes]
profilemanager skill list writer
profilemanager mcp add writer --name files -- npx -y @modelcontextprotocol/server-filesystem /data
profilemanager mcp list writer
profilemanager tool set writer read,bash,edit,write   # flat list: replaces the global one
profilemanager secret set writer files TOKEN=abc123  # MCP env, listed masked
profilemanager secret list writer
profilemanager knowledge add writer ./guide.md
profilemanager knowledge list writer
profilemanager model set writer opencode-go/deepseek-v4.1-flash
profilemanager model show writer

Isolation pattern: your global pi stays clean (no global skills or MCPs); each profile loads only its own. The profile's defaultTools (flat list in its settings.json) replace the global ones; secrets live in the profile's mcp.json env, never in global config.

Opening (isolated)

open detects a live session for the profile by code (herdr agent list, matched by folder) and reuses it instead of duplicating the tab, resetting it to a clean context (/new through the agent surface) — unless it is busy (working/blocked: left untouched and reported). Otherwise it creates the tab with PI_CODING_AGENT_DIR=<profile> and starts pi there.

profilemanager open writer
# reused:  tab: wR:tC pane: wR:pE (profile writer already live, session cleared)
# fresh:   tab: wS:t2 pane: wS:p2 (pi loading profile writer)
# no herdr: cd <profile> && PI_CODING_AGENT_DIR=<profile> pi

Auth: create copies your global ~/.pi/agent/auth.json into the profile so the session works without /login (it is a copy: a later /login inside the profile affects only that profile). Use create --no-auth for a separate login per profile. Environment API keys are inherited only if the herdr server can see them.

Inside pi

Install the package and use /profile:

pi install ./
/profile                 # picker (UI-only, zero tokens)
profilemanager open writer --dir ./my-profiles
/profile list            # UI toast, zero tokens (the agent reads PROFILE.md directly if it needs it)
/profile show writer     # UI toast, zero tokens
/profile open writer     # reuses the live tab, or opens a new one
/profile approve writer deploy   # graduates a quarantined skill

Consultative commands are UI-only on purpose: profile names and docs in model context would cost tokens on every later turn.

Task board (never block)

One board for all profiles: a JSON file per task in <vault>/.tasks/. Delegate with the tool (every profile has it — they all load this package):

  • start with an id for the target profile (one-line title, the brief in note: task id + exact commands, the other profile inherits no context). After delegating, keep working on something else — never sit waiting.
  • The assignee is notified of new work assigned to it (for everyone else, started stays noise).
  • The other profile closes it with done (the ok): files + how it verified in note. blocked when it needs a decision, failed when it cannot finish.
  • News (assigned, blocked, done, failed) is queued into the transcript (no forced model turn) plus a zero-token UI toast; the session reads it on its next natural turn.
  • Check without polling: list returns summaries (no history), show returns detail with history trimmed to the last 10.
  • The board is state; the durable notes/decisions vault is separate. Do not duplicate.

CLI fallback (same board, for outside a profile session):

profilemanager task start fix-auth --profile gordo --title "Fix auth" --note "brief + exact commands"
profilemanager task set fix-auth blocked --note "missing decision"
profilemanager task set fix-auth done --note "files + verification"
profilemanager task list [--state blocked] [--profile gordo]
profilemanager task show fix-auth

The watcher polls every 3s, starts on session_start, is cleaned on session_shutdown, and never reports a session about its own tasks. On fresh startup a profile is also shown its open tasks (offline catch-up: the no-herdr fallback).

Worked example: delegate and keep working

negro (orchestrator) needs tito (frontend) to review a landing page. Both sessions are live.

  1. In the negro session, the agent calls profile_task with: action: "start", id: "landing-review", profile: "tito", title: "Review landing copy", note: "Task landing-review. Run: npm run dev, open http://localhost:4321/senderos. Report copy issues blocking release." Result: profile_task start landing-review (profile negro): {"id":"landing-review","state":"started",...} — and negro keeps working on something else.
  2. In the tito session, the watcher queues - landing-review [assigned] — Review landing copy (check it with profile_task show landing-review). Tito reads the brief with show, does the review.
  3. Tito calls profile_task with action: "done", id: "landing-review", note: "Reviewed 18 pages: 3 copy fixes in src/pages/*.astro, verified with npm run build (0 errors)".
  4. Negro's watcher queues - landing-review [done] tito — Review landing copy. Negro reads the note on its next natural turn and moves on. No polling, no forced turns, no herdr messages anywhere.

Learning loop

Inside a profile session the agent has the profile_skill tool (create/patch/delete) to save what it learns as skills in the profile's own library. The SYSTEM.md generated by create already carries the prompt with the 3 triggers (repeatable workflow, dead ends, user correction) and the lessons, not logs rule. The linter warns, never blocks: PR/issue numbers, >60 support files, body >24k chars.

Traceability and quarantine (CLI):

profilemanager learn log writer "deploy api to staging with steps X"
profilemanager learn review writer     # groups repetition candidates
profilemanager learn promote writer --as deploy --desc "Deploys the app" --from "cases 1-2"
profilemanager learn approve writer deploy   # graduates: leaves quarantine

Every new skill (tool or promote) is born quarantined (disable-model-invocation, pi-native mechanism): it exists but the model does not auto-invoke it until approve. promote records it in memory/log.md.

Nudge and real usage (inside the session): every N tool calls (learningNudgeInterval in the profile's settings.json, default 15, 0 disables) the agent gets a passive reminder to save what it learned; reading a SKILL.md counts a view and a patch counts a patch in skills/.usage.json. At session start it warns about unreviewed entries and overdue curation (7 days).

Curator (Hermes-inspired): never deletes, archives.

profilemanager learn curate writer            # dry-run: reports stale (14d) and to-archive (30d)
profilemanager learn curate writer --apply     # moves to skills/.archive/ and leaves a ledger
profilemanager learn rollback writer           # undoes the last applied run
profilemanager learn pin writer deploy         # the curator skips it
profilemanager learn lock writer deploy        # the AGENT cannot patch/delete (only a human unlocks)

Honest limit of lock: it blocks the agent path (profile_skill), not bash writes (the same limit Hermes acknowledges).

Standard block in every profile

Every profile (new or existing) carries the same collaboration block in its SYSTEM.md, delimited by markers: task board, exact commands, queued news, no waiting, no herdr needed for handoffs. So any profile knows how to communicate even without herdr.

profilemanager sync --all          # add/refresh the block everywhere
profilemanager sync gordo tito     # only those

Idempotent: if the block is current it writes nothing, and the profile's own content (outside the markers) is untouched.

Footer

The pi footer shows perfil: <name> only while the session runs inside a profile folder (set by the extension on session_start).

Requirements

  • Node >= 22, no dependencies.
  • herdr on PATH with the server running: optional, only for opening/reusing tabs. Without herdr, open (CLI and /profile open) explains how to load the profile by hand, and the task board still works (offline catch-up on next session_start).
  • pi >= 1.4 for the extension.

Tests

npm test

Credits

No third-party code is bundled (zero dependencies). Thanks to the projects this package builds on and integrates with: herdr (terminal workspace manager for agents), pi (the coding agent hosting the extension), npm:pi-herdr (seeded into new profiles by default), npm:pi-lens (surgical code reading), @dietrichgebert/ponytail (lazy senior-dev discipline), and Hermes (inspiration for the archiving curator).

Español

Qué es

Cada perfil es una carpeta que funciona como agent dir de pi (PI_CODING_AGENT_DIR apunta ahí en open): settings, skills, MCPs, secrets, system prompt y knowledge salen del perfil; tu config global no se toca. Dos sesiones con perfiles distintos tienen cosas distintas cargadas, incluso al mismo tiempo.

Además cada perfil carga este package, que les da a todos las mismas herramientas:

  • profile_task — tablero de tareas compartido entre perfiles. Delegás con un id, el otro perfil la cierra con done (el ok). Nadie queda bloqueado esperando.
  • profile_skill — el agente guarda lo que aprende como skills en la librería de su propio perfil.
  • /profile — listar, ver, abrir perfiles, aprobar skills.
  • Un watcher que encola las novedades de tareas en el transcript (nunca fuerza un turno del modelo) más un toast UI de cero tokens.
  • Un footer perfil: <nombre>, para saber siempre en qué perfil corre cada sesión.

Regla de diseño: todo lo de coordinación es programático (archivos + código), así cuesta ~cero tokens hasta que hay trabajo real. herdr es solo ciclo de vida de sesiones (abrir/reutilizar tabs); pi core no trae subagentes, así que sin herdr el fallback es el tablero mismo — las tareas persisten y la próxima sesión las levanta sola.

Estructura de un perfil

profiles/writer/
├── PROFILE.md                  # docs: nombre, qué hace, skills, herramientas, uso
├── settings.json               # config pi del perfil (defaultTools plano, packages)
├── SYSTEM.md                   # system prompt del perfil
├── mcp.json                    # MCPs + secrets (env) del perfil, nada global
├── knowledge/                  # archivos de conocimiento del perfil
├── agents/writer.md            # definición herdr-compatible (frontmatter + prompt)
└── skills/<skill>/SKILL.md     # skills aisladas: solo las de este perfil

Uso del CLI

profilemanager create writer --desc "Redacta docs"
profilemanager list
profilemanager show writer
profilemanager open writer            # tab herdr con pi + perfil; sin herdr imprime el equivalente a mano
profilemanager open writer --dir ./mis-perfiles

Vault por defecto: M:/memory (override con --dir o PROFILEMANAGER_DIR).

Config por defecto (markdown)

Tus defaults para perfiles nuevos viven en un .md editable a mano: ~/.pi/agent/profilemanager.md (override: PROFILEMANAGER_CONFIG). profilemanager defaults show lo crea con el template si no existe.

profilemanager defaults show                                      # aplica el template si falta
profilemanager defaults edit                                       # imprime la ruta para editarla
profilemanager defaults package add pi-herdr                       # nombre global, npm:, git: o ruta
profilemanager defaults package remove pi-herdr
profilemanager defaults set defaultTools "read, grep"
profilemanager defaults set learningNudgeInterval 30
profilemanager defaults set systemPromptAppend "Responde en español."

Se aplica en create (--with/--all-global se suman a los packages por defecto). Los comentarios y secciones propias se preservan: el CLI solo reescribe la sección que toca. Este package siempre se siembra además, para que el learning loop y el tablero existan en todo perfil.

Paquetes por perfil

Los packages son parte del agent dir, así que una sesión de perfil carga solo los que declara su settings.json. create siembra este package (ruta absoluta).

profilemanager package global                       # qué hay instalado globalmente
profilemanager create writer --with pi-herdr,pi-lens   # elige por nombre (el corto alcanza si es único)
profilemanager create full --all-global              # todos los globales

profilemanager package add writer pi-herdr      # bare name -> npm:pi-herdr
profilemanager package add writer npm:@scope/x
profilemanager package add writer ./mi-paquete  # ruta local -> absoluta
profilemanager package list writer
profilemanager package remove writer pi-herdr

El npm dir (~/.pi/agent/npm) es compartido: un package ya instalado con pi install <source> se resuelve desde el perfil sin reinstalar.

Sin perfil activo nada de esto aplica: si no hay PI_CODING_AGENT_DIR, pi usa tu agent dir global y carga tus packages globales como siempre.

Editar un perfil (todo queda aislado en su carpeta)

profilemanager skill add writer ./mis-skills/notas [--as notas]
profilemanager skill list writer
profilemanager mcp add writer --name archivos -- npx -y @modelcontextprotocol/server-filesystem /datos
profilemanager mcp list writer
profilemanager tool set writer read,bash,edit,write   # lista plana: reemplaza la global
profilemanager secret set writer archivos TOKEN=abc123  # env del MCP, listado enmascarado
profilemanager secret list writer
profilemanager knowledge add writer ./guia.md
profilemanager knowledge list writer
profilemanager model set writer opencode-go/deepseek-v4.1-flash
profilemanager model show writer

Patrón de aislamiento: tu pi global queda limpio (sin skills ni MCPs globales); cada perfil carga solo lo suyo. Los defaultTools del perfil (lista plana en su settings.json) reemplazan los globales; los secrets viven en el env del mcp.json del perfil, nunca en config global.

Abrir (aislado)

open detecta por código si el perfil ya tiene sesión viva (herdr agent list, matcheo por carpeta) y la reutiliza en vez de duplicar la tab, reseteándola a contexto limpio (/new por la superficie del agente) — salvo que esté ocupada (working/blocked: se deja intacta y se informa). Si no hay sesión, crea la tab con PI_CODING_AGENT_DIR=<perfil> y arranca pi ahí.

profilemanager open writer
# reutilizada: tab: wR:tC pane: wR:pE (profile writer already live, session cleared)
# nueva:      tab: wS:t2 pane: wS:p2 (pi loading profile writer)
# sin herdr:  cd <perfil> && PI_CODING_AGENT_DIR=<perfil> pi

Auth: create copia tu ~/.pi/agent/auth.json global al perfil para que la sesión funcione sin /login (es una copia: un /login posterior dentro del perfil solo afecta a ese perfil). Usa create --no-auth si preferís login separado por perfil. Las API keys por entorno se heredan solo si las ve el servidor herdr.

Dentro de pi

Instalá el package y usá /profile:

pi install ./
/profile                 # picker (solo UI, cero tokens)
profilemanager open writer --dir ./mis-perfiles
/profile list            # toast UI, cero tokens (el agente lee PROFILE.md directo si lo necesita)
/profile show writer     # toast UI, cero tokens
/profile open writer     # reutiliza la tab viva, o abre una nueva
/profile approve writer deploy   # gradúa una skill en cuarentena

Los comandos consultivos son solo-UI a propósito: nombres y docs de perfiles en el contexto del modelo costarían tokens en cada turno posterior.

Tablero de tareas (nunca bloquear)

Un tablero para todos los perfiles: un JSON por tarea en <vault>/.tasks/. Se delega con la tool (todos los perfiles la tienen — todos cargan este package):

  • start con un id para el profile destino (título de una línea, el brief en note: id de tarea + comandos exactos, el otro perfil no hereda contexto). Después de delegar, seguí con otra cosa — nunca te quedes esperando.
  • Al asignado se le avisa el trabajo nuevo (para el resto, started sigue siendo ruido).
  • El otro perfil la cierra con done (el ok): archivos + cómo verificó en note. blocked si necesita una decisión, failed si no puede terminar.
  • Las novedades (assigned, blocked, done, failed) se encolan en el transcript (ningún turno forzado del modelo) más un toast UI de cero tokens; la sesión las lee en su próximo turno natural.
  • Revisar sin pullear: list devuelve resúmenes (sin historial), show devuelve detalle con historial recortado a los últimos 10.
  • El tablero es estado; el vault de notas y decisiones durables es otra cosa. No duplicar.

Fallback por CLI (mismo tablero, para fuera de una sesión de perfil):

profilemanager task start fix-auth --profile gordo --title "Arreglar auth" --note "brief + comandos exactos"
profilemanager task set fix-auth blocked --note "falta decisión"
profilemanager task set fix-auth done --note "archivos + verificación"
profilemanager task list [--state blocked] [--profile gordo]
profilemanager task show fix-auth

El watcher pullea cada 3s, arranca en session_start, se limpia en session_shutdown y nunca avisa a una sesión sobre sus propias tareas. Al abrir sesión el perfil también ve sus tareas abiertas (catch-up offline: el fallback sin herdr).

Ejemplo de uso: delegar y seguir trabajando

negro (orquestador) necesita que tito (frontend) revise una landing. Ambas sesiones están vivas.

  1. En la sesión de negro, el agente llama a profile_task con: action: "start", id: "landing-review", profile: "tito", title: "Review landing copy", note: "Task landing-review. Run: npm run dev, open http://localhost:4321/senderos. Report copy issues blocking release." Resultado: profile_task start landing-review (profile negro): {"id":"landing-review","state":"started",...} — y negro sigue con otra cosa.
  2. En la sesión de tito, el watcher encola - landing-review [assigned] — Review landing copy (check it with profile_task show landing-review). Tito lee el brief con show y hace la revisión.
  3. Tito llama a profile_task con action: "done", id: "landing-review", note: "Reviewed 18 pages: 3 copy fixes in src/pages/*.astro, verified with npm run build (0 errors)".
  4. El watcher de negro encola - landing-review [done] tito — Review landing copy. Negro lee la nota en su próximo turno natural y sigue. Sin pullear, sin turnos forzados, sin mensajes por herdr en ningún lado.

Learning loop

Dentro de una sesión de perfil, el agente tiene la tool profile_skill (create/patch/delete) para guardar lo que aprende como skills en la librería del perfil. El SYSTEM.md que genera create ya trae el prompt con los 3 disparadores (workflow repetible, callejones sin salida, corrección del usuario) y la regla lessons, not logs. El linter advierte (nunca bloquea): números de PR/issues, >60 archivos de soporte, cuerpo >24k chars.

Trazabilidad y cuarentena (CLI):

profilemanager learn log writer "despliegue api a staging con los pasos X"
profilemanager learn review writer     # agrupa repeticiones candidatas
profilemanager learn promote writer --as deploy --desc "Despliega la app" --from "casos 1-2"
profilemanager learn approve writer deploy   # gradúa: sale de cuarentena

Toda skill nueva (tool o promote) nace en cuarentena (disable-model-invocation, mecanismo nativo de pi): existe pero el modelo no la auto-invoca hasta approve. El promote deja constancia en memory/log.md.

Nudge y uso real (dentro de la sesión): cada N llamadas a tools (learningNudgeInterval en el settings.json del perfil, default 15, 0 apaga) el agente recibe un recordatorio pasivo de guardar lo aprendido; leer un SKILL.md suma view y un patch suma patch en skills/.usage.json. Al inicio de sesión avisa si hay entradas sin revisar y si la curación está vencida (7 días).

Curador (inspirado en Hermes): nunca borra, archiva.

profilemanager learn curate writer            # dry-run: reporta stale (14d) y a archivar (30d)
profilemanager learn curate writer --apply     # mueve a skills/.archive/ y deja ledger
profilemanager learn rollback writer           # deshace la última corrida aplicada
profilemanager learn pin writer deploy         # el curador lo saltea
profilemanager learn lock writer deploy        # el AGENTE no puede patch/delete (solo un humano desbloquea)

Límite honesto del lock: bloquea la ruta del agente (profile_skill), no escrituras por bash (mismo límite que Hermes reconoce).

Bloque estándar en todos los perfiles

Todo perfil (nuevo o existente) lleva el mismo bloque de colaboración en su SYSTEM.md, delimitado por marcadores: tablero de tareas, comandos exactos, novedades encoladas, no esperar, no hace falta herdr para los handoffs. Así cualquier perfil sabe comunicarse aunque no tenga herdr.

profilemanager sync --all          # agrega/refresca el bloque en todos
profilemanager sync gordo tito     # solo esos

Es idempotente: si el bloque ya está actual no escribe nada, y el contenido propio del perfil (fuera de los marcadores) no se toca.

Footer

El footer de pi muestra perfil: <nombre> solo cuando la sesión corre dentro de una carpeta de perfil (lo pone la extensión en session_start).

Requisitos

  • Node >= 22, sin dependencias.
  • herdr en el PATH y servidor corriendo: opcional, solo para abrir/reutilizar tabs. Sin herdr, open (CLI y /profile open) explica cómo cargar el perfil a mano, y el tablero sigue funcionando (catch-up offline en el próximo session_start).
  • pi >= 1.4 para la extensión.

Tests

npm test

Agradecimientos

Sin código de terceros vendoreado (cero dependencias). Gracias a los proyectos sobre los que este package construye e integra: herdr (gestor de workspace de terminal para agentes), pi (el coding agent que hospeda la extensión), npm:pi-herdr (sembrado por defecto en perfiles nuevos), npm:pi-lens (lectura quirúrgica de código), @dietrichgebert/ponytail (disciplina lazy senior-dev) y Hermes (inspiración del curador que archiva).