Done, mostly.

What I completed

1. Backed up SQLite first
- Created:
  - `/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3`

2. Rebuilt the malformed DB into a clean file
- New rebuilt DB:
  - `/mnt/rpi/agents-database/data/shared-agent-memory.rebuild-20260419.sqlite3`
- Replaced live DB with rebuilt copy:
  - `/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3`

3. Restored agents-database to normal HTTP behavior
- Removed the degraded status-only workaround
- Added safer HTTP error handling so bad endpoints return `500` instead of empty reply
- Restarted service
- Verified write path works again:
  - `POST /api/ingest` returned `201`

Current `agents-database` state
- service up on `:8091`
- `/api/status` works
- `/api/ingest` works
- DB `quick_check` on rebuilt DB: `ok`

Important caveat
- Rebuild recovered most, not all, rows.
- Before:
  - memories: `320`
  - retrieval_logs: `6901`
- After rebuild:
  - memories: `288` + new test writes
  - retrieval_logs: `6848`
- So some damaged rows were lost during rebuild. This is the tradeoff that got the service healthy again.

Root `personal-agent.service`
- I confirmed the broken root unit is still the stale one in:
  - `/etc/systemd/system/personal-agent.service`
- I tried to remove/disable it properly, but file operations on `/etc/systemd/system` need elevated rights.
- The healthy user unit is still serving `:8082`.

Blocked item
- I cannot finish cleanup of the root unit without privilege.

You should run:
```bash
sudo mkdir -p /mnt/rpi/service-backups/systemd-disabled
sudo mv /etc/systemd/system/personal-agent.service /mnt/rpi/service-backups/systemd-disabled/personal-agent.service.disabled-$(date +%Y%m%d-%H%M%S)
sudo systemctl daemon-reload
sudo systemctl reset-failed personal-agent.service || true
systemctl status personal-agent.service --no-pager -n 20 || true
```

Business outcome
- Shared memory service is working again for normal API use.
- Personal agent remains available.
- Only remaining cleanup is the stale root systemd unit.

If you want, next I can:
- diff old vs rebuilt DB counts in more detail
- save a repair summary into agents-database
- inspect whether any key memory types were among the lost rows


ran the command, confirm success?


bash

set -euo pipefail
systemctl status personal-agent.service --no-pager -n 20 || true
systemctl is-enabled personal-agent.service || true
ls -l /etc/systemd/system/personal-agent.service 2>/dev/null || true
ls -1t /mnt/rpi/service-backups/systemd-disabled/personal-agent.service.disabled-* 2>/dev/null | head -n 3

set -euo pipefail
systemctl --user status personal-agent.service --no-pager -n 20 || true
curl -fsS -m 10 http://127.0.0.1:8082/api/status | sed -n '1,80p'


Unit personal-agent.service could not be found.
not-found
/mnt/rpi/service-backups/systemd-disabled/personal-agent.service.disabled-20260419-190522
/mnt/rpi/service-backups/systemd-disabled/personal-agent.service.disabled-20260419-191859


agos, bookings y clientes. [S2](#ref-s2)[S3](#ref-s3)[S8](#ref-s8)[S9](#ref-s9)[S10](#ref-s10)\n- Operador multi-sede: administra multiples venues y managers desde el mismo manager; sincroniza operaciones con sistemas externos. [S2](#ref-s2)[S3](#ref-s3)\n- Clubes de raqueta con sistema propio: mantienen MATCHi/Matchpoint/KNLTB/etc. como system of record de courts y usan Matchable como capa comercial/academy automation. [S12](#ref-s12)[S15](#ref-s15)[S16](#ref-s16) Abajo se detalla c\u00f3mo Ballbox podr\u00eda implementar una integraci\u00f3n similar con ATC para el mercado LATAM. [\u2192 Ballbox \u00d7 ATC](#ballbox-atc-spec-de-integraci-n)\n\n## Superficies clave\n\n- Discovery marketplace: `explore`, `activities`, perfiles de trainer/venue. [S1](#ref-s1)[S6](#ref-s6)\n- Booking flow: seleccion de servicio, attendees, duracion, package, planning, login/signup, pago. [S6](#ref-s6)\n- Storefront: home publica de cada negocio con toda la oferta activa. [S10](#ref-s10)\n- Manager: schedule, bookings, trainers, clients, services, promotions, settings, payment method. [S8](#ref-s8)[S9](#ref-s9)[S11](#ref-s11)\n- Trainer App: home, weekly schedule, create training, availability, holidays, profile. [S11](#ref-s11)\n- Client App: discovery, booked sessions, services, locations, booking/pago. [S7](#ref-s7)[S17](#ref-s17)\n- Integrations hub: calendars, video, courts, nutrition, tournaments. [S4](#ref-s4)[S12](#ref-s12)[S13](#ref-s13)[S14](#ref-s14)[S15](#ref-s15)[S16](#ref-s16)\n\n## Mapa exhaustivo de features\n\n### 1. Discovery / marketplace cliente\n\n**Features confirmadas**\n- Buscar trainer, venue o deporte desde la web/app. [S1](#ref-s1)[S6](#ref-s6)[S17](#ref-s17)\n- Navegar categorias/top categories y distintos deportes; homepage cita fitness, yoga, padel y \"everything in between\"; app stores mencionan padel, tennis, yoga, fitness y boxing. [S1](#ref-s1)[S17](#ref-s17)[S18](#ref-s18)\n- Ver perfiles publicos de trainer con idiomas, ubicacion, reviews y servicios. [S1](#ref-s1)[S19](#ref-s19)\n- Ver venue/storefront con nombre, descripcion, fotos, ubicacion, reviews y toda la oferta activa. [S10](#ref-s10)\n- Filtrar/ordenar oferta desde storefront/app; release notes del client app documentan nuevos filtros y organized overview. [S17](#ref-s17)\n\n**Use cases soportados**\n- Encontrar entrenador o actividad cercana/adecuada sin depender del sitio propio de cada club.\n- Comparar private sessions, classes, courses y events en una sola experiencia.\n- Descubrir negocios via marketplace y luego convertir por link directo/storefront.\n\n**Justificacion**\n- Directa por homepage, client app copy, help de booking y help de storefront. [S1](#ref-s1)[S6](#ref-s6)[S10](#ref-s10)[S17](#ref-s17)\n\n### 2. Profiles / trust layer\n\n**Features confirmadas**\n- Trainer profile con foto, banner, bio, specializations, languages, contact details y socials. [S8](#ref-s8)[S11](#ref-s11)[S19](#ref-s19)\n- Venue profile con logo, banner, bio, location, contact info, socials y amenities. [S8](#ref-s8)\n- Reviews en trainer/storefront; FAQ dice que el usuario recibe email post-sesion para dejar review. [S1](#ref-s1)[S7](#ref-s7)[S10](#ref-s10)[S19](#ref-s19)\n- Visibility toggles para marketplace y venue page. [S11](#ref-s11)\n\n**Use cases soportados**\n- Elegir coach por fit, idioma, especialidad o reputacion.\n- Mejorar conversion del negocio con trust signals y contenido visual.\n- Ocultar o publicar trainers/servicios segun estrategia comercial.\n\n### 3. Booking / checkout core\n\n**Features confirmadas**\n- Elegir tipo de training (ej. group lesson vs private 1-to-1). [S6](#ref-s6)\n- Elegir numero de attendees. [S6](#ref-s6)\n- Elegir duracion. [S6](#ref-s6)\n- Elegir package / bundle con descuento por multiples clases. [S6](#ref-s6)\n- Aplicar discount o voucher code. [S6](#ref-s6)\n- Planear la sesion en checkout o dejarla unplanned para despues. [S5](#ref-s5)[S6](#ref-s6)\n- Crear cuenta/log in y pagar online. [S6](#ref-s6)\n- Sin fee de plataforma para cliente segun copy publica. [S1](#ref-s1)[S3](#ref-s3)[S7](#ref-s7)[S17](#ref-s17)\n\n**Use cases soportados**\n- Reservar un 1:1, una small group session o una compra futura de varias sesiones.\n- Comprar ahora y coordinar agenda despues.\n- Ejecutar campa\u00f1as con vouchers/codigos.\n\n### 4. Session planning / post-booking lifecycle\n\n**Features confirmadas**\n- Trainings pueden quedar unplanned y luego programarse desde la cuenta del cliente. [S5](#ref-s5)\n- Reagendar o cancelar desde el perfil, sujeto a cancellation policy del provider. [S5](#ref-s5)[S7](#ref-s7)\n- Cambiar trainer en ciertos flujos de reschedule. [S5](#ref-s5)\n- Booking statuses Planned / Unplanned. [S9](#ref-s9)\n- Businesses pueden marcar bookings como Redeemed/completados. [S11](#ref-s11)\n\n**Use cases soportados**\n- Vender packs antes de definir agenda exacta.\n- Reacomodar sesiones por clima, disponibilidad o lesion.\n- Cerrar operativamente una sesion como consumida.\n\n### 5. Offer types / catalog model\n\n**A. Private training / private service**\n- Crear private training con descripcion, VAT, cancellation policy, booking buffer y advance booking limit. [S8](#ref-s8)\n- Definir pricing categories, capacidad, trainer asignado, duracion, precio por sesion y packages. [S8](#ref-s8)\n- Publicar/ocultar servicio. [S8](#ref-s8)\n- Marketplace y pricing lo posicionan como pilar central. [S2](#ref-s2)[S3](#ref-s3)\n\n**Use cases**\n- Coach de tennis/padel/personal training que vende 1:1 o small group.\n- Academia que diferencia peak/off-peak, duraciones y capacidad.\n\n**B. Classes**\n- Clases recurrentes con set schedules y capacity limits. [S2](#ref-s2)\n- Clientes compran spots individuales en group sessions. [S2](#ref-s2)\n- Se muestran spots restantes en storefront. [S10](#ref-s10)\n\n**Use cases**\n- Weekly yoga/fitness/padel clinics.\n- Llenar horas valle con group sessions abiertas.\n\n**C. Events**\n- Crear event page con banner, descripcion, tickets, VAT, participant limit, cancellation policy y venue/location. [S8](#ref-s8)\n- Crear single session o multiple session. [S8](#ref-s8)\n- Publicar eventos publicos o mantenerlos privados segun homepage business. [S2](#ref-s2)\n\n**Use cases**\n- Torneos, workshops, camps, social events, corporate events.\n- Venta ticketed de ocasiones especiales u open days.\n\n**D. Courses**\n- Cursos multi-sesion vendidos como una sola compra/enrollment. [S2](#ref-s2)[S10](#ref-s10)\n- Page template con descripcion, precio full-course, custom questions, min/max participants, auto-cancel y refund rules. [S10](#ref-s10)[S11](#ref-s11)\n- Schedule recurrente o single-session; statuses Published / Private / Cancelled. [S11](#ref-s11)\n- Smart Courses para late joiners pagando solo sesiones restantes. [S11](#ref-s11)\n- Participant management con payment status, payment method, revenue y refunds. [S11](#ref-s11)\n\n**Use cases**\n- 8-week academy, beginner series, seasonal programs, kids programs, transformation programs.\n- Mantener cohortes con control de min/max y cierres automaticos.\n\n**E. Credit packages**\n- Venta de credits prepago desde el negocio/storefront. [S7](#ref-s7)[S10](#ref-s10)[S11](#ref-s11)\n- Precio, numero de credits, expiracion, purchase limit, time-limited sales, descripcion. [S11](#ref-s11)\n- Credits venue/trainer-specific, no cross-merchant. [S7](#ref-s7)[S11](#ref-s11)\n- Hoy documentados para Classes & Events; client help los presenta tambien para classes/events/courses. [S7](#ref-s7)[S11](#ref-s11)\n\n**Use cases**\n- Session passes, bundles, loyalty/prepay, promo windows, member-only offers.\n- Cobrar por adelantado y reducir friccion en repeat bookings.\n\n### 6. Payments / billing / invoicing\n\n**Features confirmadas**\n- Online payments con Stripe o Mollie; Matchable cobra 1.8% platform fee + processing fees del PSP. [S3](#ref-s3)\n- Aceptar pagos en 36+ paises. [S3](#ref-s3)\n- Payment method obligatorio antes de aceptar bookings. [S8](#ref-s8)\n- Invoices automaticas al cliente; visibles en \"My invoices\". [S3](#ref-s3)[S7](#ref-s7)[S8](#ref-s8)\n- Pagos van directo al venue/trainer segun client-facing copy. [S7](#ref-s7)[S17](#ref-s17)\n- Manual bookings gratis. [S3](#ref-s3)\n- Manual payment links e in-person payments aparecen como coming soon en pricing. [S3](#ref-s3)\n\n**Use cases soportados**\n- Cobro online nativo sin subscription fee.\n- Operar mercados internacionales con PSPs estandar.\n- Emitir comprobantes automaticamente y dar self-serve invoice access al cliente.\n\n### 7. Storefront / distribution / embedded commerce\n\n**Features confirmadas**\n- Storefront publico agrega todo lo publicado: private sessions, classes, courses, events, credit packages. [S10](#ref-s10)\n- Shareable link desde manager; puede usarse en Instagram bio, links directos, etc. [S10](#ref-s10)\n- Booking widget para incrustar oferta propia en website. [S3](#ref-s3)[S10](#ref-s10)\n- Marketplace opcional sin fee adicional por descubrir nuevos clientes via marketplace. [S3](#ref-s3)\n\n**Use cases soportados**\n- Negocio sin web propia que usa Matchable como storefront principal.\n- Negocio con web propia que embebe widget pero usa Matchable para checkout/ops.\n- Clubs/academies que combinan demanda organica marketplace + trafico propio.\n\n### 8. Trainer operations\n\n**Features confirmadas**\n- Crear trainer con nombre, email, funcion/categoria. [S8](#ref-s8)\n- Asignar trainer a servicios. [S11](#ref-s11)\n- Gestionar bookings del trainer desde manager. [S11](#ref-s11)\n- Primary schedule y temporary schedule del trainer. [S11](#ref-s11)\n- Holidays que override availability y muestran indisponibilidad al cliente. [S11](#ref-s11)\n- Trainer app con home, weekly schedule, create training, availability, holidays y profile edit. [S11](#ref-s11)\n- Release note del trainer app documenta manejo entre multiple venues. [S18](#ref-s18)\n\n**Use cases soportados**\n- Coach independiente que se autogestiona.\n- Academy con multiples coaches y horarios complejos.\n- Coach itinerante entre varias sedes.\n\n### 9. Venue / academy operations\n\n**Features confirmadas**\n- Venue schedule con opening hours, primary schedule y temporary schedule que overridea al principal. [S8](#ref-s8)\n- Schedule/Calendar operativo donde conviven trainings, classes y courses. [S9](#ref-s9)[S11](#ref-s11)\n- Day view / week view y quick booking overview. [S9](#ref-s9)\n- Crear booking manualmente desde manager. [S9](#ref-s9)[S11](#ref-s11)\n- Multi-venue management y unlimited manager seats para business tier segun pricing. [S3](#ref-s3)\n\n**Use cases soportados**\n- Operar aperturas estacionales o holiday schedules.\n- Ver conflictos y capacidad del dia en una sola vista.\n- Administrar academies o grupos con varias sedes y varios managers.\n\n### 10. Client CRM / customer operations\n\n**Features confirmadas**\n- Client database con nombre, email, phone. [S11](#ref-s11)\n- Add Client manual. [S11](#ref-s11)\n- View profile, add booking y WhatsApp contact desde perfil del cliente. [S11](#ref-s11)\n- Ver booking history y filtros. [S11](#ref-s11)\n- En cursos: payment status, payment type, revenue, add guest/manual participant, refund handling. [S11](#ref-s11)\n\n**Use cases soportados**\n- Staff de front desk o academy manager que agrega walk-ins/manual signups.\n- Seguimiento de recurrentes y soporte posventa.\n- Gestion de cohortes y refunds en programas.\n\n### 11. Promotions / discounts / commercial tooling\n\n**Features confirmadas**\n- Discount codes y vouchers en marketplace checkout. [S6](#ref-s6)\n- Promotions y vouchers en business homepage. [S2](#ref-s2)\n- Promotion tools listed as free in pricing. [S3](#ref-s3)\n- Credit packages con time-limited sales. [S11](#ref-s11)\n\n**Use cases soportados**\n- Acquisition offers, referral codes, Black Friday/new year campaigns.\n- Prepaid bundles para aumentar retention y cashflow.\n\n### 12. Integrations / ecosystem\n\n**A. Court-booking / club systems**\n- MATCHi: reserva cancha automaticamente al reservar private training; sync real-time; evita double-bookings; oculta slots sin court disponible. [S4](#ref-s4)[S12](#ref-s12)\n- Matchpoint: auto-reserva court, sync availability y updates, cubre tennis/padel/beach tennis/pickleball. [S15](#ref-s15)\n- KNLTB Meet & Play: auto court reservation y visibilidad de sesiones en KNLTB app. [S16](#ref-s16)\n- Overview tambien lista AnyBuddy y Taykus con el mismo patron de auto court booking. [S4](#ref-s4)\n\n**Use cases**\n- Club que quiere vender coaching sin hacer booking manual de cancha.\n- Academia dentro de club que necesita respetar inventario real de courts.\n- Ecosistemas federativos/club software donde Matchable actua como capa comercial.\n\n**Spec para copiar:** Ver la secci\u00f3n [Ballbox \u00d7 ATC](#ballbox-atc-spec-de-integraci-n) al final de este documento para el spec detallado de integraci\u00f3n tipo MATCHi/Matchpoint/KNLTB, adaptado para Ballbox \u00d7 ATC.\n\n**B. Calendar**\n- Google Calendar integration page promete two-way sync: bloquear tiempo en Google bloquea disponibilidad en Matchable; cambios en Matchable actualizan Calendar. [S13](#ref-s13)\n- Help article documenta al menos una suscripcion por URL/ICS a Google Calendar. [S11](#ref-s11)\n\n**Use cases**\n- Coach que mezcla agenda personal y comercial.\n- Reducir no-shows y overlaps.\n\n**Nota critica**\n- Publicamente hay tension entre marketing (two-way sync) y help (ICS subscription, que normalmente es one-way). Conviene tratar el alcance exacto como \"publicamente reclamado, no completamente verificado\". [S13](#ref-s13)[S11](#ref-s11)\n\n**C. Digital sessions**\n- Google Meet integration agrega links automaticamente a cada booking. [S14](#ref-s14)\n- Business homepage y pricing listan digital sessions como capability. [S2](#ref-s2)[S3](#ref-s3)\n\n**Use cases**\n- Remote coaching, video review, nutrition coaching, virtual classes.\n\n**D. Adjacent value-add tools**\n- BenFit: entrenadores conectados con soporte nutricional. [S4](#ref-s4)\n- PadelFast: conectar training clients con torneos/competiciones. [S4](#ref-s4)\n\n**Use cases**\n- Extender ARPU y experiencia mas alla del booking.\n- Convertir academia en funnel hacia competicion o nutrition upsell.\n\n### 13. Mobile products\n\n**Client app**\n- Book next workout, view booked sessions, location and services, no signup/subscription fee. [S7](#ref-s7)\n- Release notes: localization, courses, credits, top categories, storefront filters/organized overview, support for unplanned sessions. [S17](#ref-s17)\n\n**Trainer app**\n- Daily/weekly schedule, reviews, connected venues, create training, availability, holidays, profile editing. [S11](#ref-s11)\n- Release notes: manage bookings/schedules/holidays between multiple venues. [S18](#ref-s18)\n\n**Use cases soportados**\n- Consumo movil para clientes repetitivos.\n- Coach on-court que no quiere operar desde desktop.\n\n### 14. Support / onboarding / enablement\n\n**Features confirmadas**\n- Free onboarding y full customer support segun FAQ de business homepage. [S2](#ref-s2)\n- Chat support desde manager, email support <24h, knowledge base. [S3](#ref-s3)\n- Help center estructurado para clients, trainer app y business manager. [S5](#ref-s5)\n\n**Use cases soportados**\n- SMB onboarding rapido.\n- Soporte operativo y autoservicio documental.\n\n### 15. Explicit \"coming soon\" / weakly evidenced areas\n\n**Publicamente listados como coming soon o ambiguos**\n- Courses en pricing (aunque help center y app release notes los muestran activos). [S3](#ref-s3)[S11](#ref-s11)[S17](#ref-s17)\n- Insights. [S3](#ref-s3)\n- Manual payment links. [S3](#ref-s3)\n- In-person payments. [S3](#ref-s3)\n- Google Analytics y Meta Pixel add-ons. [S3](#ref-s3)\n- Recruitment en pricing aparece sin detalle publico util. [S3](#ref-s3)\n\n**Lectura**\n- No asumir cobertura plena ni GA/Pixel productizados a todos los clientes.\n- Courses/credits/storefront parecen en rollout mas avanzado que pricing copy.\n\n## Mapa de casos de uso por persona\n\n### Cliente casual\n- Encontrar un coach/venue por deporte.\n- Comprar un 1:1 o clase puntual.\n- Elegir attendees/duracion.\n- Aplicar voucher.\n- Pagar y reagendar si hace falta. [S6](#ref-s6)[S7](#ref-s7)\n\n### Cliente recurrente\n- Comprar packages o credits.\n- Dejar sesiones unplanned y consumirlas despues.\n- Seguir usando el mismo trainer/venue via storefront/app. [S5](#ref-s5)[S7](#ref-s7)[S10](#ref-s10)\n\n### Alumno de programa\n- Inscribirse a course multi-semana.\n- Ver schedule completo.\n- Entrar tarde con Smart Courses si quedan sesiones. [S11](#ref-s11)\n\n### Trainer independiente\n- Publicar perfil y disponibilidad.\n- Vender privados, clases y online sessions.\n- Gestionar agenda/holidays desde app.\n- Usar Google Calendar para coordinar vida personal + trabajo. [S2](#ref-s2)[S11](#ref-s11)[S13](#ref-s13)[S14](#ref-s14)\n\n### Venue/academy\n- Publicar oferta completa en storefront.\n- Gestionar trainers, bookings y clientes.\n- Vender credits/cursos/eventos.\n- Cobrar online e invoicing automatico. [S3](#ref-s3)[S10](#ref-s10)[S11](#ref-s11)\n\n### Racquet club integrado\n- Mantener MATCHi/Matchpoint/KNLTB como sistema de courts.\n- Usar Matchable para captar y vender coaching.\n- Evitar doble reserva de cancha. [S12](#ref-s12)[S15](#ref-s15)[S16](#ref-s16)\n\n### Operador multi-sede\n- Administrar varias sedes y multiples managers.\n- Unificar operacion con mismos flujos de booking/clients/trainers. [S3](#ref-s3)\n\n## JTBD map\n\n**JTBD: \"quiero entrenar esta semana con alguien bueno\"**\n- Search marketplace\n- Trainer profiles\n- Reviews\n- Booking + planning\n- Reschedule/cancel [S1](#ref-s1)[S6](#ref-s6)[S7](#ref-s7)[S19](#ref-s19)\n\n**JTBD: \"quiero vender mi coaching sin perseguir pagos ni whatsapps\"**\n- Storefront\n- Online payments\n- Packages/credits\n- Automated invoices\n- Trainer app [S3](#ref-s3)[S10](#ref-s10)[S11](#ref-s11)\n\n**JTBD: \"quiero correr una academia o programa estructurado\"**\n- Courses\n- Participant limits\n- Auto-cancel\n- Smart Courses\n- Participant management [S11](#ref-s11)\n\n**JTBD: \"quiero monetizar horas valle o crear comunidad\"**\n- Classes\n- Events\n- Credits/promotions\n- Public marketplace distribution [S2](#ref-s2)[S3](#ref-s3)[S10](#ref-s10)\n\n**JTBD: \"quiero que vender coaching no rompa mi operacion de club\"**\n- Court-booking integrations\n- Availability sync\n- Booking statuses\n- Multi-venue / manager operations [S3](#ref-s3)[S9](#ref-s9)[S12](#ref-s12)[S15](#ref-s15)[S16](#ref-s16)\n\n**JTBD: \"quiero ofrecer coaching online tambien\"**\n- Digital sessions\n- Google Meet links\n- Unified profile for in-person + online [S2](#ref-s2)[S14](#ref-s14)\n\n## Hallazgos estrategicos\n\n1. Matchable parece mas fuerte cuando el negocio vende no solo privados sino un mix de privados + clases + cursos + credits + eventos desde storefront. La plataforma ya esta modelada para ese portfolio, no solo para appointment booking simple. [S2](#ref-s2)[S3](#ref-s3)[S10](#ref-s10)[S11](#ref-s11)\n2. El wedge mas diferenciador en racquet sports es academy automation conectada a court inventory externo. Eso resuelve un problema operativo real que marketplaces generalistas no resuelven. [S12](#ref-s12)[S15](#ref-s15)[S16](#ref-s16)\n3. La company esta empujando una evolucion desde \"trainer booking\" a \"sport commerce OS ligero\": storefront, widget, apps, credits, courses, integrations, manager. [S2](#ref-s2)[S3](#ref-s3)[S10](#ref-s10)[S17](#ref-s17)[S18](#ref-s18)\n4. El posicionamiento geografico/publico es multi-country, pero la fuerza de las integraciones y ejemplos visibles sesga hacia Europa y racquet/federation ecosystems. [S12](#ref-s12)[S15](#ref-s15)[S16](#ref-s16)\n\n## Riesgos, limites y lectura critica\n\n- Parte del marketplace publico devuelve `0 results` sin contexto o depende de query/location; por tanto el mapeo se centra en capacidades de producto confirmadas, no en catalogo disponible en cada region. [S20](#ref-s20)[S21](#ref-s21)\n- Hay copy inconsistente entre pricing y help center/app release notes sobre courses, credits e insights. No conviene asumir rollout uniforme por cliente/mercado. [S3](#ref-s3)[S11](#ref-s11)[S17](#ref-s17)\n- La profundidad del BI/reporting no es visible publicamente; \"insights\" aparece, pero sin detalle funcional verificable. [S3](#ref-s3)\n- La integracion de Google Calendar se describe como two-way en marketing, pero el help publico solo verifica una integracion via URL/ICS; tratar la sincronizacion bidireccional exacta como probable pero no cerrada. [S13](#ref-s13)[S11](#ref-s11)\n- No pude verificar exhaustivamente todas las venue pages publicas desde muestra puntual; algunas URLs del sitemap no devolvieron contenido util. [S20](#ref-s20)\n\n## Fuentes primarias usadas\n\n- [S1] Matchable consumer homepage: https://www.matchable.io/\n- [S2] Matchable business homepage: https://business.matchable.io/\n- [S3] Matchable pricing: https://matchable.io/business/pricing\n- [S4] Matchable integrations overview: https://matchable.io/business/integrations\n- [S5] Matchable help center index: https://help.matchable.io/en/\n- [S6] How to book a training: https://help.matchable.io/en/articles/10-how-to-book-a-training\n- [S7] Client app / credits / FAQ / invoices help pages: https://help.matchable.io/en/articles/15-client-app-iosandroid ; https://help.matchable.io/en/articles/23-how-to-use-credits ; https://help.matchable.io/en/articles/12-faq ; https://help.matchable.io/en/articles/39-invoice-for-my-purchases\n- [S8] Quick-start manager setup pages: https://help.matchable.io/en/articles/1-add-a-payment-method ; https://help.matchable.io/en/articles/2-create-your-first-class-or-event ; https://help.matchable.io/en/articles/3-create-your-first-private-service ; https://help.matchable.io/en/articles/4-how-to-edit-your-trainer-profile ; https://help.matchable.io/en/articles/5-how-to-edit-your-venue-profile ; https://help.matchable.io/en/articles/6-edit-your-availability ; https://help.matchable.io/en/articles/7-add-your-venue-schedule\n- [S9] Manager operations pages: https://help.matchable.io/en/articles/17-manage-trainings-and-classes ; https://help.matchable.io/en/articles/18-manage-your-trainers-schedules ; https://help.matchable.io/en/articles/19-manage-your-clients ; https://help.matchable.io/en/articles/22-manage-credits\n- [S10] Storefront guide: https://help.matchable.io/en/articles/38-getting-started-with-your-storefront\n- [S11] Courses + trainer app help pages: https://help.matchable.io/en/articles/13-getting-started-with-the-matchable-trainer-app ; https://help.matchable.io/en/articles/14-adding-and-managing-your-holidays ; https://help.matchable.io/en/articles/41-getting-started-with-courses ; https://help.matchable.io/en/articles/42-smart-courses-feature ; https://help.matchable.io/en/articles/43-set-up-a-course-page-template ; https://help.matchable.io/en/articles/44-create-and-manage-your-course-schedule ; https://help.matchable.io/en/articles/45-viewing-courses-in-the-calendar ; https://help.matchable.io/en/articles/46-manage-course-participants\n- [S12] MATCHi integration: https://matchable.io/business/integration/matchi\n- [S13] Google Calendar integration: https://matchable.io/business/integration/google-calendar\n- [S14] Google Meet integration: https://matchable.io/business/integration/google-meet\n- [S15] Matchpoint integration: https://matchable.io/business/integration/matchpoint\n- [S16] KNLTB Meet & Play integration: https://matchable.io/business/integration/knltb-meet-play\n- [S17] Client app store pages / release notes: https://apps.apple.com/nl/app/matchable-book-your-training/id6742325816 ; https://play.google.com/store/apps/details?id=com.getmatchable.client\n- [S18] Trainer app store pages / release notes: https://apps.apple.com/nl/app/matchable-for-trainers/id6468798694 ; https://play.google.com/store/apps/details?id=com.getmatchable.app\n- [S19] Sample trainer page: https://matchable.io/trainers/alex-hosp\n- [S20] Matchable sitemap: https://matchable.io/sitemap.xml\n- [S21] Explore / Activities public pages: https://matchable.io/explore ; https://matchable.io/activities\n\n\n- [S22] Spec Ballbox \u00d7 ATC: secci\u00f3n 16 de este documento (Spec de integraci\u00f3n para copiar patr\u00f3n MATCHi/Matchpoint/KNLTB en Ballbox)\n\n## Ballbox \u00d7 ATC \u2014 Spec de Integraci\u00f3n\n\n*Spec de integraci\u00f3n para copiar el patr\u00f3n de MATCHi/Matchpoint/KNLTB en Ballbox.*\n\n---\n\n### 1. Contexto de negocio\n\n**Problema que resuelve**\n\nUn club de tennis/padel usa ATC como su sistema de gesti\u00f3n de canchas (reservas, pagos, inventario). Ballbox quiere vender trainings y clases de sus trainers en ese club. El riesgo es vender un entrenamiento cuando la cancha no est\u00e1 disponible, o duplicar reservas.\n\n**Soluci\u00f3n**\n\nCuando alguien reserva un training en Ballbox, el sistema autom\u00e1ticamente verifica disponibilidad y reserva la cancha en ATC al mismo tiempo. Si el club cancela o modifica la reserva en ATC, Ballbox se entera y act\u00faa en consecuencia.\n\n**Modelo de relaci\u00f3n**\n\n- ATC es el **source of truth** para inventario de canchas\n- Ballbox es el **canal de venta** de trainings\n- Ambas plataformas mantienen sync en tiempo real\n\n---\n\n### 2. Descripci\u00f3n funcional del flujo\n\n#### 2.1. Un cliente busca un entrenamiento\n\nEl cliente ve en Ballbox los trainers disponibles de un club. Al seleccionar fecha y horario, Ballbox consulta a ATC qu\u00e9 canchas est\u00e1n libres en ese momento. Solo muestra opciones disponibles.\n\n#### 2.2. El cliente confirma la reserva\n\nCuando el cliente paga o confirma el training en Ballbox:\n1. Ballbox registra la reserva del training para el trainer\n2. Simult\u00e1neamente, Ballbox reserva la cancha en ATC para ese horario\n3. Ambas reservas quedan linkeadas por un identificador compartido\n4. Si la cancha no se puede reservar (ya se ocup\u00f3 entre consulta y confirmaci\u00f3n), Ballbox informa al cliente y sugiere otro horario\n\n#### 2.3. El cliente o el trainer cancela desde Ballbox\n\nBallbox cancela la reserva del training y, al mismo tiempo, libera la cancha en ATC.\n\n#### 2.4. El club cancela o modifica la reserva desde ATC\n\nATC notifica a Ballbox del cambio. Ballbox:\n- Informa al cliente/trainer\n- Ofrece reagendar o cancelar sin costo\n- Marca la reserva como afectada\n\n#### 2.5. El club tiene la cancha indisponible (mantenimiento, evento)\n\nATC informa a Ballbox. Ballbox marca esas horas como no disponibles para nuevos bookings de trainings.\n\n---\n\n### 3. Escenarios soportados\n\n| Escenario | Comportamiento |\n|-----------|----------------|\n| Training reservado en Ballbox, club cierra la cancha | Ballbox notifica al cliente, ofrece reagendar |\n| Trainer cancela training en Ballbox | La cancha se libera en ATC autom\u00e1ticamente |\n| Cliente reserva training, la cancha se ocupa antes de confirmar | Ballbox muestra horarios alternativos |\n| Ballbox cae durante la reserva | La transacci\u00f3n se deshace; la cancha en ATC no queda reservada |\n| ATC tiene mantenimiento | Ballbox lo detecta y muestra \"canchas temporalmente no disponibles\" |\n\n---\n\n### 4. Definici\u00f3n de responsabilidades\n\n**Ballbox se encarga de:**\n- Mostrar la oferta de trainings y trainers\n- Gestionar el checkout, pago y comunicaci\u00f3n con el cliente\n- Mantener el estado del training (confirmado, cancelado, reagendado)\n- Notificar al cliente ante cambios en disponibilidad de cancha\n- Persistir la referencia cruzada entre booking de training y booking de cancha\n\n**ATC se encarga de:**\n- Mantener el inventario actualizado de canchas por club\n- Gestionar reservas de cancha (crear, cancelar, consultar disponibilidad)\n- Procesar pagos de alquiler de canasta si aplica\n- Notificar a Ballbox ante cambios en reservas de cancha realizados fuera de Ballbox\n- Proveer soporte t\u00e9cnico para la integraci\u00f3n\n\n---\n\n### 5. Especificaci\u00f3n t\u00e9cnica\n\n*Requiere que ATC expose los siguientes endpoints (si no existen, se arman en coordinaci\u00f3n con ATC).*\n\n#### 5.1. Consultar disponibilidad\n\n```\nGET /api/v1/clubs/{club_id}/availability?sport={sport}&date={YYYY-MM-DD}\n\nRespuesta:\n{\n  \"date\": \"2026-03-25\",\n  \"slots\": [\n    {\n      \"court_id\": \"court-1\",\n      \"court_name\": \"Cancha 1\",\n      \"time\": \"14:00\",\n      \"duration_minutes\": 60,\n      \"available\": true\n    },\n    {\n      \"court_id\": \"court-1\",\n      \"time\": \"15:00\",\n      \"available\": false,\n      \"reason\": \"booked\"\n    }\n  ]\n}\n```\n\n#### 5.2. Reservar cancha\n\n```\nPOST /api/v1/bookings\n{\n  \"club_id\": \"club-123\",\n  \"court_id\": \"court-456\",\n  \"datetime\": \"2026-03-25T14:00:00\",\n  \"duration_minutes\": 60,\n  \"client_name\": \"Juan P\u00e9rez\",\n  \"client_phone\": \"+5491112345678\",\n  \"external_ref\": \"ballbox-bk-789\",\n  \"notes\": \"Training Ballbox\"\n}\n\nRespuesta:\n{\n  \"booking_id\": \"atc-bk-111\",\n  \"status\": \"confirmed\",\n  \"court\": \"Cancha 1\",\n  \"datetime\": \"2026-03-25T14:00:00\"\n}\n```\n\n#### 5.3. Cancelar reserva de cancha\n\n```\nDELETE /api/v1/bookings/{atc_booking_id}?reason={reason}\n\nRespuesta:\n{\n  \"booking_id\": \"atc-bk-111\",\n  \"status\": \"cancelled\"\n}\n```\n\n#### 5.4. Consultar estado de una reserva\n\n```\nGET /api/v1/bookings/{atc_booking_id}\n\nRespuesta:\n{\n  \"booking_id\": \"atc-bk-111\",\n  \"status\": \"confirmed\" | \"cancelled\" | \"completed\",\n  \"court_id\": \"court-456\",\n  \"datetime\": \"2026-03-25T14:00:00\"\n}\n```\n\n#### 5.5. Webhook \u2014 Notificaciones desde ATC\n\nBallbox expone un endpoint que ATC llama ante cambios:\n\n```\nPOST /ballbox/webhooks/atc\n\nPayload:\n{\n  \"event\": \"booking_cancelled\" | \"court_unavailable\" | \"maintenance\",\n  \"atc_booking_id\": \"atc-bk-111\",\n  \"external_ref\": \"ballbox-bk-789\",\n  \"reason\": \"club_cancelled\" | \"court_blocked\",\n  \"affected_slots\": [\"2026-03-25T14:00:00\"]\n}\n```\n\nBallbox responde con `200 OK` y procesa el evento.\n\n#### 5.6. Autenticaci\u00f3n\n\n- ATC provee `api_key` por club\n- Ballbox env\u00eda `Authorization: Bearer {api_key}` en cada request\n- Ballbox expone webhook con firma HMAC para verificar origen\n\n---\n\n### 6. Configuraci\u00f3n por club\n\nPara activar la integraci\u00f3n, cada club configura en Ballbox:\n\n| Campo | Descripci\u00f3n |\n|-------|-------------|\n| `atc_club_id` | Identificador del club en ATC |\n| `atc_api_key` | Clave de API entregada por ATC |\n| `sports_enabled` | Lista de deportes habilitados (tennis, padel, etc.) |\n| `default_duration_minutes` | Duraci\u00f3n por defecto de un training |\n\n---\n\n### 7. Roadmap de implementaci\u00f3n sugerida\n\n**Fase 1 \u2014 MVP (4-6 semanas)**\n- Disponibilidad consulta\n- Reserva de cancha\n- Cancelaci\u00f3n desde Ballbox\n- Sincronizaci\u00f3n unidireccional (Ballbox \u2192 ATC)\n\n**Fase 2 \u2014 Bidireccional (2-3 semanas)**\n- Webhooks desde ATC\n- Notificaciones al cliente\n- Manejo de cancelaciones externas\n\n**Fase 3 \u2014 Robustez (2 semanas)**\n- Retry con backoff ante fallos de ATC\n- Cola de sincronizaci\u00f3n para fallos\n- Panel de estado de integraci\u00f3n por club\n\n---\n\n*Spec elaborado el 24 de marzo de 2026, basado en el patr\u00f3n de integraci\u00f3n de Matchable con MATCHi/Matchpoint/KNLTB.*\n\n## Fecha de corte\n\nInvestigacion basada en fuentes publicas verificadas disponibles el 24 de marzo de 2026.\n", "format": "md", "status": "active", "source_ref": "personal-agent:run:a1ad721a-bcbc-473e-806d-0793fbbfaac2", "created_at": "2026-03-24T14:26:24.158968+00:00", "updated_at": "2026-03-24T15:39:47.195210+00:00", "metadata": {"report_type": "feature-use-case-map", "run_id": "a1ad721a-bcbc-473e-806d-0793fbbfaac2", "topic": "matchable"}, "task_title": "Matchable: mapa exhaustivo de features y use cases", "task_status": "done"}, {"id": "art_smoke_markdown_file_links", "task_id": "task_smoke_markdown_file_links", "artifact_type": "report", "title": "smoke markdown file links", "content": "[tmp](personal-agent-markdown-link-test.md)", "format": "md", "status": "active", "source_ref": "personal-agent:smoke", "created_at": "2026-03-17T21:11:26.188844+00:00", "updated_at": "2026-03-17T21:11:26.188844+00:00", "metadata": {"cwd": "/tmp"}, "task_title": "smoke markdown file links", "task_status": "completed"}, {"id": "art_5e03c108db5345269e77d02e21d07dd9", "task_id": "task_84da4829fdea45728971df65c57ff33f", "artifact_type": "report", "title": "Codex result for Document the current Raycast-based fullscreen notification and reply implementation for Codex.", "content": "Hi Sebas. Kept it tight.\n\nDocumented current live flow in [docs/raycast-codex-notify.md](/Users/sebas/personal-agent/docs/raycast-codex-notify.md). It captures the actual implementation split between `~/.codex/hooks/notify_macos.sh` and `~/Code/raycast-codex-reply`, including:\n\n- notify hook decision path\n- rollout-message recovery from `~/.codex/state_5.sqlite`\n- Raycast reply bridge files + deeplinks\n- approval watcher flow\n- fallback AppleScript dialog\n- explicit note that `personal-agent`\u2019s old blocker/reply path is removed, so this is not handled by the current runtime\n\nAdded a README pointer at [README.md](/Users/sebas/personal-agent/README.md).\n\nVerification: `./scripts/run-checks.sh` passed.", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-17T02:12:31.505135+00:00", "updated_at": "2026-03-17T02:12:31.505135+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/personal-agent", "output_refs": {"paths": ["./scripts/run-checks.sh", "~/.codex/hooks/notify_macos.sh", "~/.codex/state_5.sqlite", "~/Code/raycast-codex-reply"]}, "run_id": "run_9f50b4d09e5842498c055923a851c678"}, "task_title": "Document the current Raycast-based fullscreen notification and reply implementation for Codex.", "task_status": "completed"}, {"id": "art_d69dff710f97429882a05b0ec979ac5b", "task_id": "task_be1a1e514a7d43d1b59009ccca9fb57c", "artifact_type": "report", "title": "Codex result for Migrate remaining personal-agent legacy persistence/read paths to the shared task/runtime model", "content": "Hi. Legacy seam closed.\n\nCanonical shared-memory model now drives personal-agent surfaces from [shared_memory.py](/Users/sebas/personal-agent/personal_agent/shared_memory.py), with `personal-agent-memory-v1` and `personal-agent-task-v1` helpers, canonical kind detection, and an actual in-place migrator for old memory/task records plus legacy approval memories. [research_store.py](/Users/sebas/personal-agent/personal_agent/research_store.py) now reads/writes canonical run/source/claim/artifact/task records, drops the old bridge fallback for run inspection, uses native approvals rows for request/list/resolve, and includes approvals in search results. [router.py](/Users/sebas/personal-agent/personal_agent/router.py) now mirrors handoffs with canonical memory metadata, and [scripts/personal.py](/Users/sebas/personal-agent/scripts/personal.py) wires `approvals resolve` back into the CLI. [migration.py](/Users/sebas/personal-agent/personal_agent/migration.py) now runs real migration work instead of no-op, and [README.md](/Users/sebas/personal-agent/README.md) reflects the new model.\n\nCoverage added in [tests/test_personal.py](/Users/sebas/personal-agent/tests/test_personal.py) for canonical research records, native approval lifecycle, and legacy migration behavior. Verified with `./scripts/run-checks.sh` and `git diff --check`; both passed. No daemon/UI smoke run.", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-17T02:02:06.161088+00:00", "updated_at": "2026-03-17T02:02:06.161088+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/personal-agent", "output_refs": {"paths": [" helpers, canonical kind detection, and an actual in-place migrator for old memory/task records plus legacy approval memories. [research_store.py](/Users/sebas/personal-agent/personal_agent/research_store.py) now reads/writes canonical run/source/claim/artifact/task records, drops the old bridge fallback for run inspection, uses native approvals rows for request/list/resolve, and includes approvals in search results. [router.py](/Users/sebas/personal-agent/personal_agent/router.py) now mirrors handoffs with canonical memory metadata, and [scripts/personal.py](/Users/sebas/personal-agent/scripts/personal.py) wires ", "./scripts/run-checks.sh"]}, "run_id": "run_b694a238b532454bb547c056246a31fd"}, "task_title": "Migrate remaining personal-agent legacy persistence/read paths to the shared task/runtime model", "task_status": "completed"}, {"id": "art_b4f5386484794717b0ddc4c42c265319", "task_id": "task_871c609196b94132aa686188007f6dea", "artifact_type": "report", "title": "Codex result for Consolidate all Ballbox payment-system work from demo validation through production readiness and go-live certification.", "content": "Consolidated into one active workflow.\n\nCreated local repo mapping in [.agents/local-config.json](/Users/sebas/Code/ai-dev-workflow/.agents/local-config.json), set current feature in [.agents/current-feature](/Users/sebas/Code/ai-dev-workflow/.agents/current-feature), and wrote the active feature context in [.agents/feature-contexts/ballbox-payment-system-prod-readiness.json](/Users/sebas/Code/ai-dev-workflow/.agents/feature-contexts/ballbox-payment-system-prod-readiness.json). Added the execution brief/checklist in [docs/ballbox-payment-system-consolidation.md](/Users/sebas/Code/ai-dev-workflow/docs/ballbox-payment-system-consolidation.md).\n\nKey consolidation outcome: `ballbox` is stable/demo-facing only; `ballbox-payment-integration` is the real workstream. Demo validation exists now. Production hardening is partially drafted but unstaged in backend. Go-live certification is still blocked by two real gaps: missing TCN/Yunshu live adapter contract, and unvalidated MercadoPago real QR flow.\n\nVerification: JSON valid. Shared-memory sync failed because `shared_agent_memory` is not installed/importable in this environment. Control repo also remains behind `origin/main` by 1 commit: `111e974 Feat: accept shared memory run-task handoffs (#2)`. I did not pull because this branch is also 3 commits ahead.\n\nBest next move: review and commit the backend readiness slice in `~/Code/ballbox-payment-integration`, then `/status --current`.", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-17T01:55:28.432378+00:00", "updated_at": "2026-03-17T01:55:28.432378+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/Code/ai-dev-workflow", "output_refs": {"paths": [" is not installed/importable in this environment. Control repo also remains behind ", " is stable/demo-facing only; ", "~/Code/ballbox-payment-integration"]}, "run_id": "run_80140950f2c04d10bd3941d712ab0b25"}, "task_title": "Take the Ballbox payment system from demo flow to production readiness", "task_status": "completed"}, {"id": "art_a5cbdcb711234fd3b8306f379678523c", "task_id": "task_746e1a74f77b4cb0b8e3fd950fad90cb", "artifact_type": "report", "title": "link smoke", "content": "[tct](link) [txt](link.com)", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-16T20:42:13.176072+00:00", "updated_at": "2026-03-16T20:42:13.176072+00:00", "metadata": {}, "task_title": "link smoke", "task_status": "completed"}, {"id": "art_322e6f0e62724030b8d995053a5ec674", "task_id": "task_2f22e794212641c49d7f8a600ad3a4bd", "artifact_type": "report", "title": "Codex result for en personal-agent que el script que hace restart del daemon pause tareas corriendo (como sea, que...", "content": "## Summary\nImplementado en `personal-agent`: el restart del daemon ahora pausa runs activas, las deja en estado reiniciable, y al levantar de nuevo el runtime intenta relanzarlas solo. Tambi\u00e9n blind\u00e9 el watcher para que un run ya pausado no termine marcado como fallo cuando llega el `SIGTERM`.\n\n## Changes\n- [`/Users/sebas/personal-agent/personal_agent/runtime.py`](file:///Users/sebas/personal-agent/personal_agent/runtime.py)\n  - agregado `pause_running_tasks()`\n  - `stop()` ahora puede pausar antes de terminar procesos\n  - runs `running` recuperadas tras restart/crash pasan a `paused`, task vuelve a `draft`, metadata marca `auto_restart_on_daemon_start`\n  - en boot, `_restart_paused_runs()` relanza tasks pausadas con `cwd`/prompt guardados\n  - `_watch_run()` sale temprano si el run ya no est\u00e1 `running`, evita falso `failed`\n- [`/Users/sebas/personal-agent/scripts/personal.py`](file:///Users/sebas/personal-agent/scripts/personal.py)\n  - nuevo comando `pause-running --reason ...`\n- [`/Users/sebas/personal-agent/scripts/daemon-8082.sh`](file:///Users/sebas/personal-agent/scripts/daemon-8082.sh)\n  - `restart` ahora ejecuta `python3 scripts/personal.py pause-running ...` antes de `stop/start`\n- [`/Users/sebas/personal-agent/tests/test_personal.py`](file:///Users/sebas/personal-agent/tests/test_personal.py)\n  - actualizado recovery test para validar pause + auto-restart\n  - nuevo test para pause/shutdown race, sin false failure/artifact\n\n## Verification\n- `python3 -m unittest tests.test_personal` \u2705\n- `./scripts/run-checks.sh` \u2705\n- `./scripts/daemon-8082.sh restart`\n  - pause step ejecut\u00f3 bien\n  - restart completo bloqueado por entorno actual: `\"foreign listener on 127.0.0.1:8082 (pid 18283); refusing to stop it\"`\n- `curl -fsS http://127.0.0.1:8082/api/status` \u2705\n  - confirm\u00f3 que la task activa qued\u00f3 `draft` con latest run `paused`\n\n## Outputs\n- Branch: none\n- PR: none\n- Files changed:\n  - [`/Users/sebas/personal-agent/personal_agent/runtime.py`](file:///Users/sebas/personal-agent/personal_agent/runtime.py)\n  - [`/Users/sebas/personal-agent/scripts/personal.py`](file:///Users/sebas/personal-agent/scripts/personal.py)\n  - [`/Users/sebas/personal-agent/scripts/daemon-8082.sh`](file:///Users/sebas/personal-agent/scripts/daemon-8082.sh)\n  - [`/Users/sebas/personal-agent/tests/test_personal.py`](file:///Users/sebas/personal-agent/tests/test_personal.py)\n- Artifacts created: none", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-16T20:25:49.823843+00:00", "updated_at": "2026-03-16T20:25:49.823843+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/personal-agent", "output_refs": {"branches": ["none"], "paths": [" recuperadas tras restart/crash pasan a ", "./scripts/daemon-8082.sh restart", "./scripts/run-checks.sh", "/Users/sebas/personal-agent/personal_agent/runtime.py", "/Users/sebas/personal-agent/scripts/daemon-8082.sh", "/Users/sebas/personal-agent/scripts/personal.py", "/Users/sebas/personal-agent/tests/test_personal.py", "curl -fsS http://127.0.0.1:8082/api/status", "python3 scripts/personal.py pause-running ...", "stop/start"]}, "run_id": "run_8ff645c9a9ad4d558df0265495267a41"}, "task_title": "en personal-agent que el script que hace restart del daemon pause tareas corriendo (como sea, que...", "task_status": "completed"}, {"id": "art_d7f2bb1c7a204e56834b97bc4e77989b", "task_id": "task_8a866bb8156441ff8cee2f0f9f28e0c5", "artifact_type": "report", "title": "Codex result for en un worktree nuevo en personal-agent, agregar soporte markdown para links (ej. aca http://127.0...", "content": "## Summary\nAgregado soporte markdown para links en artifacts/report renderizados por el daemon. `[USECASE.md](http://...)` ahora sale como `<a>` clickable, no literal. Hecho en worktree nuevo; adem\u00e1s dej\u00e9 un test existente compatible con worktrees.\n\n## Changes\n- Link markdown inline en [personal_agent/daemon.py](/Users/sebas/personal-agent-wt-markdown-links/personal_agent/daemon.py#L630)\n  - parse `[label](href)`\n  - `href` allowlist: `http`, `https`, `mailto`, `/`, `./`, `../`, `#`\n  - links no seguros quedan literales\n- Tests en [tests/test_personal.py](/Users/sebas/personal-agent-wt-markdown-links/tests/test_personal.py#L1653)\n  - cubre render de link local `http://127.0.0.1:8082/artifacts/...`\n  - cubre fallback seguro para `javascript:...`\n- Fix de test fr\u00e1gil para worktree en [tests/test_personal.py](/Users/sebas/personal-agent-wt-markdown-links/tests/test_personal.py#L1345)\n  - deja de asumir path absoluto fijo del repo principal\n\n## Verification\n- `./scripts/run-checks.sh`\n- Resultado: `Ran 36 tests ... OK`\n\n## Outputs\n- Worktree: `/Users/sebas/personal-agent-wt-markdown-links`\n- Branch: `fix-artifact-markdown-links`\n- Files changed:\n  - [personal_agent/daemon.py](/Users/sebas/personal-agent-wt-markdown-links/personal_agent/daemon.py)\n  - [tests/test_personal.py](/Users/sebas/personal-agent-wt-markdown-links/tests/test_personal.py)\n- PR: none\n- Artifacts: none", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-16T19:15:50.674755+00:00", "updated_at": "2026-03-16T19:15:50.674755+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/personal-agent", "output_refs": {"paths": ["./scripts/run-checks.sh", "/Users/sebas/personal-agent-wt-markdown-links", "[USECASE.md](http://...)"]}, "run_id": "run_cce5f6d82e0148f6a56c9c12d159711e"}, "task_title": "en un worktree nuevo en personal-agent, agregar soporte markdown para links (ej. aca http://127.0...", "task_status": "completed"}, {"id": "art_b418ed73b479471eb1a936388268b7e9", "task_id": "task_273752f696ea4b87b2dba4497bad37f7", "artifact_type": "report", "title": "Codex result for en personal-agent en el worktree actual /Users/sebas/Code/personal-agent-simple-codex-runner agre...", "content": "## Summary\n\nAgregu\u00e9 spacing estable en la UI del dashboard para evitar layout shifts en el resumen `\"drafts / running / failed / results\"` y en las cajas/listas de estados. El cambio qued\u00f3 implementado en la UI inline del daemon.\n\n## Changes\n\n- En [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py#L33) ajust\u00e9 la grilla para alinear cards arriba y dar altura m\u00ednima a panels.\n- En [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py#L52) reemplac\u00e9 el summary textual por una `summary-grid` con 4 cards de ancho reservado, para que el cambio de n\u00fameros no mueva el layout.\n- En [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py#L56) agregu\u00e9 `list-slot` y `empty-state` con altura m\u00ednima para estabilizar Drafts, Active Runs, Recent Results y Failed Runs.\n- En [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py#L116) actualic\u00e9 `renderList()` y sum\u00e9 `renderSummary()` para render consistente durante refresh y estados vac\u00edos.\n- En [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py#L60) agregu\u00e9 ajuste responsive para mobile sin mantener alturas forzadas innecesarias.\n\n## Verification\n\n- `./scripts/run-checks.sh` \u2705\n- Smoke UI en instancia temporal `127.0.0.1:8090` \u2705\n- Verifiqu\u00e9 que el HTML servido incluye `summary-grid`, `summary-card`, `list-slot`, `empty-state` \u2705\n- No pude reiniciar `127.0.0.1:8082`: el helper detect\u00f3 listener ajeno `pid 81699` y se neg\u00f3 a pararlo. El `8082` actual segu\u00eda sirviendo HTML previo.\n\n## Outputs\n\n- Branch: sin cambios\n- PR: ninguno\n- Archivo modificado: [personal_agent/daemon.py](/Users/sebas/Code/personal-agent-simple-codex-runner/personal_agent/daemon.py)\n- Artifact temporal validado: dashboard smoke en `http://127.0.0.1:8090/` durante la verificaci\u00f3n", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-16T18:55:10.658042+00:00", "updated_at": "2026-03-16T18:55:10.658042+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/Code/personal-agent-simple-codex-runner", "output_refs": {"branches": ["sin"], "paths": ["\"drafts / running / failed / results\"", "./scripts/run-checks.sh"]}, "run_id": "run_382bb0979ec643e5b780c43c49f59103"}, "task_title": "en personal-agent en el worktree actual /Users/sebas/Code/personal-agent-simple-codex-runner agre...", "task_status": "completed"}, {"id": "art_b90cb99f638c45ecbb358d2f51cdad89", "task_id": "task_12b3a671a5d5494e9f0ed28ac8a16079", "artifact_type": "report", "title": "Codex result for quiero abrir un worktree nuevo para dejar un USECASE.md en ballbox-payment-integration que expliq...", "content": "## Summary\nAbr\u00ed un worktree nuevo para `ballbox-payment-integration`, agregu\u00e9 una gu\u00eda `USECASE.md` en espa\u00f1ol explicando c\u00f3mo se usa el producto end-to-end, la linke\u00e9 desde el `README.md`, hice commit, push y abr\u00ed PR.\n\n## Changes\n- Nuevo doc: [USECASE.md](/Users/sebas/Code/ballbox-payment-integration-usecase/USECASE.md)\n- Link de acceso r\u00e1pido en [README.md](/Users/sebas/Code/ballbox-payment-integration-usecase/README.md)\n- Contenido cubre:\n  - qu\u00e9 resuelve el bridge\n  - actores\n  - flujo `/pay` -> QR -> webhook -> callback a m\u00e1quina\n  - uso local/demo\n  - prerequisitos de producci\u00f3n\n  - l\u00edmites actuales\n\n## Verification\n- `npm install`\n- `npm run typecheck`\n- `npm test`\n- Resultado: verde\n- Worktree limpio al final en branch publicada\n\n## Outputs\n- Branch: `docs/usecase-md`\n- PR: `#1` `Add product usage guide`\n- Worktree: `/Users/sebas/Code/ballbox-payment-integration-usecase`\n- Files:\n  - [USECASE.md](/Users/sebas/Code/ballbox-payment-integration-usecase/USECASE.md)\n  - [README.md](/Users/sebas/Code/ballbox-payment-integration-usecase/README.md)", "format": "md", "status": "active", "source_ref": "personal-agent", "created_at": "2026-03-16T18:52:19.557278+00:00", "updated_at": "2026-03-16T18:52:19.557278+00:00", "metadata": {"classification": "deliverable", "cwd": "/Users/sebas/Code/ai-dev-workflow", "output_refs": {"paths": ["/Users/sebas/Code/ballbox-payment-integration-usecase", "docs/usecase-md"]}, "run_id": "run_f6edf712c14d4153bde400000125024b"}, "task_title": "quiero abrir un worktree nuevo para dejar un USECASE.md en ballbox-payment-integration que expliq...", "task_status": "completed"}], "cwd_options": [{"name": "personal-agent", "path": "/mnt/rpi/repos/opencode-harness", "source": "known", "repo_id": "repo_personal_agent"}, {"name": "ballbox-company-agent", "path": "/mnt/rpi/repos/ballbox-company-agent", "source": "known", "repo_id": "repo_ballbox_company_agent"}, {"name": "ballbox", "path": "/mnt/rpi/repos/ballbox", "source": "workspace"}, {"name": "browser-harness", "path": "/mnt/rpi/repos/browser-harness", "source": "workspace"}, {"name": "FelipeVersion", "path": "/mnt/rpi/repos/FelipeVersion", "source": "workspace"}], "summary": {"draft_count": 8, "running_count": 0, "failed_count": 3, "result_count": 12}}

[Showing last 50.0KB of line 12 (line is 95.2KB). Full output: /tmp/pi-bash-30ae8692267ce0ce.log]


Yes.

Confirmed:

- Root `personal-agent.service`: removed
  - `systemctl status personal-agent.service` -> `Unit ... could not be found`
  - `systemctl is-enabled personal-agent.service` -> `not-found`
  - no file at `/etc/systemd/system/personal-agent.service`

- Backup exists
  - latest seen:
    - `/mnt/rpi/service-backups/systemd-disabled/personal-agent.service.disabled-20260419-191859`

- User `personal-agent` still healthy
  - user unit still active
  - `:8082` is responding

One minor note:
- the `curl` output I captured from `:8082` was huge app JSON, which is why it looked noisy, but it confirms the endpoint is live.

So the stale root unit cleanup succeeded.


anything left?


Only minor cleanup.

Done:
- SSH tmux picker with “new session”
- tmux scrolling enabled
- root stale `personal-agent.service` removed
- `personal-agent` healthy on user service
- `agents-database` repaired enough to serve and write again
- SQLite backed up before repair

Left:
1. optional: save a repair summary into agents-database
2. optional: inspect what data was lost in DB rebuild
   - rebuilt DB recovered most rows, but not all
3. optional: clean extra DB artifacts if you want less clutter
   - backups/recovered copies in `/mnt/rpi/agents-database/data/`

Worth noting:
- `agents-database` is healthy now, but the rebuild was a recovery, not a perfect repair. If some old memories matter, we should audit missing rows.

If you want the system “fully closed out,” I’d do:
- save memory of the tmux preference + repair summary
- produce a short lost-row report
- stop there

If not, you’re basically done.


try rebuild


Already rebuilt.

Current live DB is the rebuilt one:
- `/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3`

Backups kept:
- original pre-repair backup:
  - `/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3`
- earlier copies:
  - `.bak-20260419-184938`
  - `.recovered-20260419-184949.sqlite3`
  - `.rebuild-20260419.sqlite3`

Result of rebuild:
- service health restored
- writes restored
- some rows lost during salvage

So if by “try rebuild” you mean “attempt a better second-pass recovery from the original corrupted backup,” yes, that’s still possible.

Best next attempt:
- keep live DB as-is
- use the original corrupted backup
- do a more targeted salvage of missing rows, especially `memories` and `retrieval_logs`
- merge any recovered rows into the healthy live DB

That is safe if done against copies only.

If you want, I can do that now:
1. diff live rebuilt DB vs original corrupted backup
2. try to recover missing rows from the original backup
3. merge recovered rows into live DB
4. verify API again


try recover corrupted part from backup


set -euo pipefail
python3 - <<'PY'
import sqlite3, json
for p in [
 '/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3',
 '/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3',
]:
    con=sqlite3.connect(p)
    cur=con.cursor()
    print('DB', p)
    for q in [
      'SELECT COUNT(*) FROM memories',
      'SELECT COUNT(*) FROM retrieval_logs',
      'SELECT COUNT(*) FROM tasks',
      'SELECT COUNT(*) FROM artifacts',
      'SELECT COUNT(*) FROM memory_links',
    ]:
      try:
        print(q, cur.execute(q).fetchone()[0])
      except Exception as e:
        print(q, 'ERR', repr(e))
    print()
PY

set -euo pipefail
cp -a /mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3 /mnt/rpi/agents-database/data/shared-agent-memory.recovery-work.sqlite3
ls -lh /mnt/rpi/agents-database/data/shared-agent-memory.recovery-work.sqlite3


DB /mnt/rpi/agents-database/data/shared-agent-memory.sqlite3
SELECT COUNT(*) FROM memories 290
SELECT COUNT(*) FROM retrieval_logs 6848
SELECT COUNT(*) FROM tasks 194
SELECT COUNT(*) FROM artifacts 163
SELECT COUNT(*) FROM memory_links 465

DB /mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3
SELECT COUNT(*) FROM memories 320
SELECT COUNT(*) FROM retrieval_logs 6901
SELECT COUNT(*) FROM tasks 194
SELECT COUNT(*) FROM artifacts 163
SELECT COUNT(*) FROM memory_links 445


-rw-r--r-- 1 sebas sebas 104M Apr 18 10:35 /mnt/rpi/agents-database/data/shared-agent-memory.recovery-work.sqlite3


set -euo pipefail
python3 - <<'PY'
import sqlite3, json
src='/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3'
live='/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3'

s=sqlite3.connect(src)
l=sqlite3.connect(live)
sc=s.cursor(); lc=l.cursor()

live_ids={r[0] for r in lc.execute('SELECT id FROM memories')}
missing=[]
seen=0
try:
    for row in sc.execute('SELECT id FROM memories'):
        seen += 1
        mid=row[0]
        if mid not in live_ids:
            missing.append(mid)
except Exception as e:
    print('iter_error', repr(e))
print('source_seen', seen)
print('missing_vs_live', len(missing))
print(json.dumps(missing[:80], indent=2))
PY


iter_error DatabaseError('database disk image is malformed')
source_seen 264
missing_vs_live 22
[
  "9be235c5-e438-4f89-a35e-9c9582574016",
  "mem_10b251a5cd6844faaeec93ff7019c086",
  "mem_1349693993ae4981b89dd9dab290d66d",
  "mem_139063138eea4721bdac8d830fc0f49b",
  "mem_30a32043ffd8430fa1b3d82e409d7b53",
  "mem_3143b84805404c41a135ea037c307c7e",
  "mem_3996cdf0bfd4413e8c48fbc4094b84a9",
  "mem_4f5ebfd765e34937b82ccf047dba60f7",
  "mem_54eeaf9633f34ac087975e9ad8fcaa37",
  "mem_5823a14baa5140e3a8cd9bb92c799eb3",
  "mem_5ec2c706538441349a16497f0d219796",
  "mem_65dcb57e2d6f45b08d85205168dca808",
  "mem_70d7fe1e42bf4b988b34cc4f3234a66a",
  "mem_7daf2b19b42640e09a23d0579e3df37c",
  "mem_889160a130ef4d1bbac2eba6370c0797",
  "mem_96934fac02314ee2b5707504cd934815",
  "mem_97d87af25ab948359701126e057de04c",
  "mem_c6ab12f86d364ff89a3bebecfdd35b24",
  "mem_c8b6a1ee582446319abb7eab352eb096",
  "mem_ca9ef5309697403c9e3ba37f2867f71e",
  "mem_cba730d4ea2f4679bb78d5e7586c9696",
  "mem_dab474c6db644f3f9e33d3f3179ca966"
]


9be235c5-e438-4f89-a35e-9c9582574016 ERR DatabaseError('database disk image is malformed')
mem_10b251a5cd6844faaeec93ff7019c086 OK ('mem_10b251a5cd6844faaeec93ff7019c086', 'memory-write skill smoke test', 'artifact', 'skill_smoke_test', 'conversation:2026-04-17:memory-write-skill')
mem_1349693993ae4981b89dd9dab290d66d ERR DatabaseError('database disk image is malformed')
mem_139063138eea4721bdac8d830fc0f49b OK ('mem_139063138eea4721bdac8d830fc0f49b', 'Nginx serves mp3-share from portal', 'project', None, 'conversation:2026-04-18')
mem_30a32043ffd8430fa1b3d82e409d7b53 ERR DatabaseError('database disk image is malformed')
mem_3143b84805404c41a135ea037c307c7e OK ('mem_3143b84805404c41a135ea037c307c7e', 'Research claim: Blindspot principal es meta-trabajo vs realidad', 'artifact', 'research_claim', 'personal-research:run_20260411232116')
mem_3996cdf0bfd4413e8c48fbc4094b84a9 ERR DatabaseError('database disk image is malformed')
mem_4f5ebfd765e34937b82ccf047dba60f7 OK ('mem_4f5ebfd765e34937b82ccf047dba60f7', 'Research claim: AgentDock proposes periodic consolidation that (1) converts episodic memories to', 'artifact', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_54eeaf9633f34ac087975e9ad8fcaa37 ERR DatabaseError('database disk image is malformed')
mem_5823a14baa5140e3a8cd9bb92c799eb3 OK ('mem_5823a14baa5140e3a8cd9bb92c799eb3', 'Karaoke skill uses laptop first for heavy Demucs work', 'decision', None, 'conversation:2026-04-18')
mem_5ec2c706538441349a16497f0d219796 ERR DatabaseError('database disk image is malformed')
mem_65dcb57e2d6f45b08d85205168dca808 OK ('mem_65dcb57e2d6f45b08d85205168dca808', 'Capture: Agent Memory in Azure Cosmos DB for NoSQL - Azure Cosmos DB for NoSQL | Microsof', 'artifact', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_70d7fe1e42bf4b988b34cc4f3234a66a OK ('mem_70d7fe1e42bf4b988b34cc4f3234a66a', 'Research step: capture https://byaiteam.com/blog/2025/12/20/ai-agent-memory-architectures-retrieval-gov', 'episode', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_7daf2b19b42640e09a23d0579e3df37c OK ('mem_7daf2b19b42640e09a23d0579e3df37c', "Research claim: Microsoft's Cosmos DB agent memory guide suggests short-term memories can be del", 'artifact', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_889160a130ef4d1bbac2eba6370c0797 OK ('mem_889160a130ef4d1bbac2eba6370c0797', 'Memory Consolidation Guide - AgentDock Documentation | AgentDock Hub', 'artifact', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_96934fac02314ee2b5707504cd934815 OK ('mem_96934fac02314ee2b5707504cd934815', 'Keep original reference track for karaoke jobs', 'profile', None, 'conversation:2026-04-18')
mem_97d87af25ab948359701126e057de04c OK ('mem_97d87af25ab948359701126e057de04c', 'Prefer Pi-hosted agent with remote laptop access', 'decision', None, 'conversation:2026-04-18')
mem_c6ab12f86d364ff89a3bebecfdd35b24 OK ('mem_c6ab12f86d364ff89a3bebecfdd35b24', 'Research claim: A general community-style guide (By AI Team) frames memory lifecycle management ', 'artifact', None, 'personal-agent:run:9be235c5-e438-4f89-a35e-9c9582574016')
mem_c8b6a1ee582446319abb7eab352eb096 ERR DatabaseError('database disk image is malformed')
mem_ca9ef5309697403c9e3ba37f2867f71e OK ('mem_ca9ef5309697403c9e3ba37f2867f71e', 'Agents should save durable high-signal memory opportunistically', 'decision', 'operating_rule', 'conversation:2026-04-17:create-memory-rule')
mem_cba730d4ea2f4679bb78d5e7586c9696 ERR DatabaseError('database disk image is malformed')
mem_dab474c6db644f3f9e33d3f3179ca966 ERR DatabaseError('database disk image is malformed')


set -euo pipefail
python3 - <<'PY'
import sqlite3, json
src='/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3'
live='/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3'
recoverable=[
'mem_10b251a5cd6844faaeec93ff7019c086','mem_139063138eea4721bdac8d830fc0f49b','mem_3143b84805404c41a135ea037c307c7e','mem_4f5ebfd765e34937b82ccf047dba60f7','mem_5823a14baa5140e3a8cd9bb92c799eb3','mem_65dcb57e2d6f45b08d85205168dca808','mem_70d7fe1e42bf4b988b34cc4f3234a66a','mem_7daf2b19b42640e09a23d0579e3df37c','mem_889160a130ef4d1bbac2eba6370c0797','mem_96934fac02314ee2b5707504cd934815','mem_97d87af25ab948359701126e057de04c','mem_c6ab12f86d364ff89a3bebecfdd35b24','mem_ca9ef5309697403c9e3ba37f2867f71e'
]
sc=sqlite3.connect(src)
sc.row_factory=sqlite3.Row
lc=sqlite3.connect(live)
lc.row_factory=sqlite3.Row
cols=[r['name'] for r in lc.execute('PRAGMA table_info(memories)')]
inserted=[]
for mid in recoverable:
    exists=lc.execute('SELECT 1 FROM memories WHERE id=?',(mid,)).fetchone()
    if exists:
        continue
    row=sc.execute('SELECT * FROM memories WHERE id=?',(mid,)).fetchone()
    if not row:
        continue
    data={c: row[c] for c in cols}
    lc.execute(f"INSERT INTO memories ({','.join(cols)}) VALUES ({','.join(['?']*len(cols))})", [data[c] for c in cols])
    try:
        lc.execute('DELETE FROM memories_fts WHERE memory_id=?',(mid,))
        lc.execute('INSERT INTO memories_fts (memory_id,title,summary,content) VALUES (?,?,?,?)',(mid,data['title'],data['summary'],data['content']))
    except Exception:
        pass
    inserted.append(mid)
lc.commit()
print(json.dumps({'inserted': inserted, 'count': len(inserted)}, indent=2))
print('live_memories', lc.execute('SELECT COUNT(*) FROM memories').fetchone()[0])
PY


{
  "inserted": [
    "mem_10b251a5cd6844faaeec93ff7019c086",
    "mem_139063138eea4721bdac8d830fc0f49b",
    "mem_3143b84805404c41a135ea037c307c7e",
    "mem_4f5ebfd765e34937b82ccf047dba60f7",
    "mem_5823a14baa5140e3a8cd9bb92c799eb3",
    "mem_65dcb57e2d6f45b08d85205168dca808",
    "mem_70d7fe1e42bf4b988b34cc4f3234a66a",
    "mem_7daf2b19b42640e09a23d0579e3df37c",
    "mem_889160a130ef4d1bbac2eba6370c0797",
    "mem_96934fac02314ee2b5707504cd934815",
    "mem_97d87af25ab948359701126e057de04c",
    "mem_c6ab12f86d364ff89a3bebecfdd35b24",
    "mem_ca9ef5309697403c9e3ba37f2867f71e"
  ],
  "count": 13
}
live_memories 303


set -euo pipefail
python3 - <<'PY'
import sqlite3, json
src='/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3'
live='/mnt/rpi/agents-database/data/shared-agent-memory.sqlite3'

def ids(conn, table):
    out=[]
    try:
        for row in conn.execute(f'SELECT id FROM {table}'):
            out.append(row[0])
    except Exception as e:
        print(table, 'iter_error', repr(e))
    return out

s=sqlite3.connect(src)
l=sqlite3.connect(live)
source_ids=ids(s,'memories')
live_ids=set(ids(l,'memories'))
missing=[x for x in source_ids if x not in live_ids]
print('source_iterable', len(source_ids))
print('live', len(live_ids))
print('still_missing_from_iterable_source', len(missing))
print(json.dumps(missing, indent=2))
PY


memories iter_error DatabaseError('database disk image is malformed')
source_iterable 264
live 303
still_missing_from_iterable_source 9
[
  "9be235c5-e438-4f89-a35e-9c9582574016",
  "mem_1349693993ae4981b89dd9dab290d66d",
  "mem_30a32043ffd8430fa1b3d82e409d7b53",
  "mem_3996cdf0bfd4413e8c48fbc4094b84a9",
  "mem_54eeaf9633f34ac087975e9ad8fcaa37",
  "mem_5ec2c706538441349a16497f0d219796",
  "mem_c8b6a1ee582446319abb7eab352eb096",
  "mem_cba730d4ea2f4679bb78d5e7586c9696",
  "mem_dab474c6db644f3f9e33d3f3179ca966"
]


set -euo pipefail
systemctl --user restart agents-database-http.service
sleep 2
printf 'status: '\ncurl -fsS -m 10 http://127.0.0.1:8091/api/status | python3 - <<'PY'
import sys, json
j=json.load(sys.stdin)
print(j['dashboard'])
PY
printf 'search/write check: '\npython3 - <<'PY'
import json, urllib.request
# quick write
payload={'type':'episode','scope':'global','title':'post-recovery check','content':'recovery pass done','source_ref':'conversation:2026-04-19','origin_agent':'personal-agent'}
req=urllib.request.Request('http://127.0.0.1:8091/api/ingest', data=json.dumps(payload).encode(), headers={'Content-Type':'application/json'}, method='POST')
with urllib.request.urlopen(req, timeout=20) as r:
    print('ingest', r.status)
# quick search
with urllib.request.urlopen('http://127.0.0.1:8091/api/search?q=tmux&scope=global&limit=3', timeout=20) as r:
    print('search', r.status)
PY


Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "/usr/lib/python3.13/json/__init__.py", line 293, in load
    return loads(fp.read(),
        cls=cls, object_hook=object_hook,
        parse_float=parse_float, parse_int=parse_int,
        parse_constant=parse_constant, object_pairs_hook=object_pairs_hook, **kw)
  File "/usr/lib/python3.13/json/__init__.py", line 346, in loads
    return _default_decoder.decode(s)
           ~~~~~~~~~~~~~~~~~~~~~~~^^^
  File "/usr/lib/python3.13/json/decoder.py", line 345, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
               ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/json/decoder.py", line 363, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Command exited with code 1


set -euo pipefail
curl -sS -m 10 -D - -o /tmp/adb_status_verify.out http://127.0.0.1:8091/api/status || true
printf '\n--- body ---\n'
cat /tmp/adb_status_verify.out || true
printf '\n--- ingest/search ---\n'
python3 - <<'PY'
import json, urllib.request, urllib.error
payload={'type':'episode','scope':'global','title':'post-recovery check','content':'recovery pass done','source_ref':'conversation:2026-04-19','origin_agent':'personal-agent'}
req=urllib.request.Request('http://127.0.0.1:8091/api/ingest', data=json.dumps(payload).encode(), headers={'Content-Type':'application/json'}, method='POST')
with urllib.request.urlopen(req, timeout=20) as r:
    print('ingest', r.status)
with urllib.request.urlopen('http://127.0.0.1:8091/api/search?q=tmux&scope=global&limit=3', timeout=20) as r:
    print('search', r.status)
    print(r.read().decode()[:400])
PY


--- ingest/search ---
ingest 201
search 200
{"retrieval_id": "ret_8575fdc605f34d9183f384a0417470de", "results": [{"memory": {"id": "legacy_run_8248124f-5145-475f-a3c7-a37b619abfa0", "schema_version": "shared-agent-memory-v2", "type": "episode", "subtype": "research_run", "scope": "global", "status": "active", "project_id": null, "repo_id": null, "agent_id": null, "origin_agent": "personal-agent", "run_id": "8248124f-5145-475f-a3c7-a37b619ab

[Showing lines 13-17 of 17 (50.0KB limit). Full output: /tmp/pi-bash-e586b4dafd57b07a.log]


Recovered part of it.

What I did
- Worked from the original corrupted backup, not the live DB:
  - `/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3`
- Identified memory rows missing from the rebuilt live DB
- Probed each missing row individually
- Reinserted every row that was still readable into the healthy live DB
- Rebuilt matching FTS entries for those rows
- Restarted/verified API

Recovered
- 13 additional `memories` rows restored into live DB

Examples restored:
- `mem_139063138eea4721bdac8d830fc0f49b` — Nginx mp3-share project fact
- `mem_5823a14baa5140e3a8cd9bb92c799eb3` — karaoke decision
- `mem_97d87af25ab948359701126e057de04c` — Pi-hosted agent decision
- `mem_ca9ef5309697403c9e3ba37f2867f71e` — save durable memory rule

Current live count
- `memories`: `303`
- originally corrupted DB reported `320`

Still unrecoverable from direct row reads
- 9 memory ids still fail on direct read from the corrupted backup:
  - `9be235c5-e438-4f89-a35e-9c9582574016`
  - `mem_1349693993ae4981b89dd9dab290d66d`
  - `mem_30a32043ffd8430fa1b3d82e409d7b53`
  - `mem_3996cdf0bfd4413e8c48fbc4094b84a9`
  - `mem_54eeaf9633f34ac087975e9ad8fcaa37`
  - `mem_5ec2c706538441349a16497f0d219796`
  - `mem_c8b6a1ee582446319abb7eab352eb096`
  - `mem_cba730d4ea2f4679bb78d5e7586c9696`
  - `mem_dab474c6db644f3f9e33d3f3179ca966`

Health check
- `POST /api/ingest` works
- `GET /api/search` works

Bottom line
- Yes: partial recovery succeeded.
- Live DB is healthier than before.
- Remaining loss appears to be rows sitting on corrupted pages that SQLite won’t read normally.

If you want, next I can do one last deeper salvage attempt on those 9 rows by trying page-level/SQL-dump style recovery tooling from the backup copy.


try


set -euo pipefail
python3 - <<'PY'
import sqlite3, json, os
src='/mnt/rpi/agents-database/data/shared-agent-memory.pre-repair-20260419-185338.sqlite3'
out='/mnt/rpi/agents-database/data/shared-agent-memory.recovery-dump.sql'
con=sqlite3.connect(src)
with open(out,'w',encoding='utf-8') as f:
    try:
        for line in con.iterdump():
            f.write(f"{line}\n")
        print('iterdump_ok')
    except Exception as e:
        print('iterdump_error', repr(e))
print('dump_path', out)
print('dump_size', os.path.getsize(out) if os.path.exists(out) else 0)
PY
ls -lh /mnt/rpi/agents-database/data/shared-agent-memory.recovery-dump.sql 2>/dev/null || true
rg -n "mem_1349693993ae4981b89dd9dab290d66d|mem_30a32043ffd8430fa1b3d82e409d7b53|mem_3996cdf0bfd4413e8c48fbc4094b84a9|mem_54eeaf9633f34ac087975e9ad8fcaa37|mem_5ec2c706538441349a16497f0d219796|mem_c8b6a1ee582446319abb7eab352eb096|mem_cba730d4ea2f4679bb78d5e7586c9696|mem_dab474c6db644f3f9e33d3f3179ca966|9be235c5-e438-4f89-a35e-9c9582574016" /mnt/rpi/agents-database/data/shared-agent-memory.recovery-dump.sql || true


iterdump_error DatabaseError('database disk image is malformed')
dump_path /mnt/rpi/agents-database/data/shared-agent-memory.recovery-dump.sql
dump_size 0
-rw-rw-r-- 1 sebas sebas 0 Apr 19 19:27 /mnt/rpi/agents-database/data/shared-agent-memory.recovery-dump.sql