Cómo funcionan las tareas
La parte técnica de las tareas: una tabla, claves, vínculos, reservas y la detección de duplicados prevista.
Comprende cómo se guarda una tarea, de dónde sale su clave y cómo encontrará la herramienta tareas que pueden ser la misma. Sirve a desarrolladores y autores de agentes que leen el almacén local o construyen sobre las tareas. Primero van los datos; después, lo previsto: claves por tipo, reservas y la detección de duplicados con un ejemplo resuelto. Para el uso diario, empieza por Tareas.
El diseño viene del plan de tareas aprobado en cli-core (WireCatLabs/cli-core#1216). Cada sección dice si describe lo que ya existe o lo que está previsto.
Los datos
Ahora. Una tabla, tasks, guarda todas las tareas del almacén local. La lista completa de
columnas está en el modelo de datos.
| Qué | Cómo se guarda |
|---|---|
| Tipo | question, request, mention, promise para tareas encontradas en mensajes; bug, feature, chore para tareas de proyecto |
| Plan | título, descripción, prioridad de 0 (urgente) a 4 (baja), fecha límite, próxima revisión, tarea padre |
| Estado | open, done o dismissed, con quién la cerró y por qué |
| Fuente | un localizador de mensaje o nota, nunca el texto del mensaje, así que un mensaje borrado no deja nada |
| Valoración | useful o not_useful, según valoraste una tarea que planteó un agente |
| Personas | asignaciones con un rol: responsable, revisor, observador |
| Historial | task_events: creada, estado, asignada, fecha límite, prioridad, movida, vinculada, desvinculada |
| Vínculos | filas de links a mensajes, chats, personas, documentos, notas y direcciones, con un tipo |
Cada tarea pertenece a un proyecto. Una tarea encontrada en un mensaje va al proyecto de entrada de su
cuenta; una tarea de proyecto va a un proyecto que creaste tú. Un vínculo a un mensaje tiene el tipo
related-to, evidence o created-from. Un vínculo entre dos tareas también es una fila de
links, con el tipo blocked-by, follow-up-of o related-to. Estos vínculos solo funcionan entre
tareas de tus propios proyectos, y solo tú los aplicas. Antes de escribir un blocked-by o un
follow-up-of, una consulta recursiva comprueba que no cierra un ciclo.
Previsto. Campos nuevos: category, claimed_by, claimed_until. Una tarea pasa a ser un tipo
más en la tabla taggings que ya existe, así que los comandos y filtros tags sirven en las tareas. Tipos de vínculo
nuevos: duplicate-of y discovered-from. Dos tablas de texto completo nuevas, task_words y
task_stems, hechas como las de las notas. Cada cambio de categoría pasa a ser una fila de
task_events del tipo category.
Identificadores y claves
Ahora. Una tarea de proyecto tiene una clave formada por la clave del proyecto y un número:
HRB-12. El número sale de un contador en la fila del proyecto, tomado en la misma escritura que la
inserción:
UPDATE projects SET tasks_count = tasks_count + 1 WHERE id = ? RETURNING key, tasks_countUna tarea encontrada en un mensaje tiene un id interno (un UUID) y una clave del proyecto de entrada
de su cuenta: IN, 8 caracteres hexadecimales y un número, como IN1A2B3C4D-17. tasks list muestra el principio del id; tasks show y tasks agenda muestran la clave.
Previsto. Una tarea nueva encontrada en un mensaje recibe, en lugar de la clave de entrada, una
clave legible por tipo: Q-15 pregunta, REQ-22 petición, M-7 mención, PR-4 promesa. Las tareas
anteriores conservan su clave IN…. Cada tipo tiene su propia fila de contador, tomada
en la misma escritura que la inserción, como en los proyectos. El UUID y los dos tipos de clave se
resuelven como task:<…>. La categoría es un campo, no parte de la clave, así que corregir una categoría nunca
rompe una clave citada en un chat o una nota.
Por qué no identificadores hash. beads usa
identificadores hash como bd-a3f8, que se alargan a medida que crece la base de datos
(internal/idgen/hash.go).
Los hash resuelven muchos escritores sobre muchas copias que se combinan después. Aquí hay un almacén
por ordenador y un escritor cada vez, así que un contador da claves cortas que una persona puede leer.
Si algún día los almacenes se sincronizan entre dispositivos, las claves llevarán un prefijo de
dispositivo; esa decisión queda para más adelante.
Reservas (previsto)
Dos agentes, o un agente y tú, no deben trabajar a la vez en la misma tarea. tasks claim ejecuta
una sola sentencia dentro de BEGIN IMMEDIATE:
UPDATE tasks SET claimed_by = ?, claimed_until = ?
WHERE id = ? AND status = 'open' AND (claimed_by IS NULL OR claimed_until < ?)
RETURNING idSi no vuelve ninguna fila, otro tiene la tarea; el error dice quién y hasta cuándo. SQLite deja
entrar a un solo escritor cada vez entre procesos, así que la reserva es atómica entre tg, max,
memo y los agentes del mismo ordenador. La reserva es un préstamo con plazo: termina sola (a
los 30 minutos por defecto), así que un agente que se cae nunca retiene una tarea para siempre.
tasks release la devuelve antes; cerrar una tarea también la libera. tasks agenda oculta las
tareas reservadas por otros salvo que --claimed las pida.
La detección de duplicados (prevista)
La comprobación se ejecuta con cada tarea nueva y con cada mensaje nuevo vinculado a una tarea. Corre en cli-core, sobre SQLite, sin embeddings y sin llamar a un proveedor de IA.
En palabras: la tarea nueva se normaliza; una coincidencia segura la une enseguida; si no, se reúnen candidatas, cada una se compara campo a campo, se suman los pesos y el total decide: crear o preguntar. Una puntuación nunca fusiona dos tareas; decide un agente o una persona. Cada respuesta se guarda para ajustar los pesos más adelante.
1. Normalizar
- El título y la descripción pasan por el normalizador de la búsqueda: minúsculas, sin diacríticos,
ёpasa aе, los grupos de cifras se unen (15 000pasa a15000). - Un lematizador Snowball por escritura (ruso, inglés, español) reduce cada palabra a su raíz; se quitan las palabras vacías. El resultado es el conjunto de raíces.
- El título normalizado también se corta en trigramas de caracteres, todas las ventanas de tres letras. Atrapan errores de escritura y formas que el lematizador no ve.
- Unos patrones extraen la firma: un importe con su moneda (un número junto a
₽,руб,€,$,usd), una fecha límite (fechas y frases comoby Fridayoдо пятницы) y la persona (person:<id>de quien envió el mensaje de origen, a través deidentities). Un agente puede pasarlos él mismo (--amount,--due,--person).
Las raíces y el texto normalizado van a task_words y task_stems, que actualizan las mismas
escrituras.
2. Coincidencias seguras
Solo una coincidencia une sin puntuar: la misma regla sobre el mismo mensaje, que mantiene un índice único sobre fuente y regla. No se crea nada nuevo, y la respuesta dice a qué tarea y por qué.
En el primer borrador también eran seguras una respuesta y el mismo título. En el conjunto de pruebas se equivocaron 6 veces de 11: una respuesta que añade una petición nueva, el «+1» de otra persona, el mismo título para otro trabajo. Ahora una respuesta es un campo con peso (paso 4), y el mismo título es solo una puntuación de texto alta.
3. Candidatas
Las candidatas son la unión de consultas baratas:
SELECT task_id FROM task_stems
WHERE task_stems MATCH '<stems joined by OR>'
ORDER BY bm25(task_stems) LIMIT 20más las tareas de la misma persona, de la misma categoría y de la misma cadena de respuestas, todas
consultas simples con índice. Solo cuentan las tareas abiertas o cerradas en los últimos 14 días. La
unión tiene unas 40 tareas como mucho. bm25()
es la función de orden de SQLite FTS5.
Dos reglas evitan perder buenas candidatas:
- Compartir chat no basta nunca para ser candidata. Un chat tiene muchas tareas sin relación, y una misma petición puede llegar a un grupo y a un chat privado.
- Una sola consulta nunca es la puerta. Graphiti perdía duplicados exactos porque tomaba las candidatas de una sola búsqueda con un corte (getzep/graphiti#1734).
4. Comparar campo a campo
| Campo | Comparación | Niveles |
|---|---|---|
| Texto | Jaccard (elementos comunes divididos entre todos los distintos) sobre los conjuntos de raíces de título y descripción, y sobre los trigramas del título; cuenta el valor más alto | 0,6 o más · de 0,3 a 0,6 · menos de 0,3 |
| Persona | person:<id> | misma · distinta · desconocida |
| Importe | valor y moneda | igual · distinto · falta |
| Categoría | nombre | misma · distinta |
| Fecha límite | días de diferencia | 2 o menos · más · falta |
| Cadena de respuestas | los mensajes de origen se responden entre sí | sí · no |
| Tiempo | días entre las dos tareas | 3 o menos · de 4 a 14 · más |
5. Sumar los pesos
La puntuación sigue el modelo de Fellegi–Sunter para enlazar registros. Cada nivel tiene un peso
log2(m / u). Aquí m es con qué frecuencia muestran ese nivel los duplicados reales, y u, con qué
frecuencia lo muestran tareas sin relación. Un nivel habitual entre duplicados sube la suma; uno raro
la baja. La guía de Splink sobre el modelo de Fellegi–Sunter
explica el método.
Los primeros pesos se fijaron a mano y después se ajustaron con un conjunto de pruebas sintético:
| Nivel | Peso |
|---|---|
| Texto: 0,6 o más / de 0,3 a 0,6 / menos de 0,3 | +5 / +5 / −2 |
| Persona: misma / distinta / desconocida | +1 / −3 / 0 |
| Importe: igual / distinto / falta | +6 / −4 / 0 |
| Categoría: misma / distinta | +1 / −1 |
| Fecha límite: 2 días o menos / más / falta | +4 / −1 / 0 |
| Cadena de respuestas: sí / no | +6 / 0 |
| Tiempo: 3 días o menos / de 4 a 14 días / más | +2 / −2 / −3 |
Ningún duplicado del conjunto tenía otra persona, otro importe u otra categoría, así que contar daría a esos niveles −5 o −6 sin ninguna observación. Conservan los valores fijados a mano.
Bandas: 10 o más es «probable», de 5 a 9 es «quizá», menos de 5 es «distinta». «Probable» solo pone una candidata la primera de la lista; nada se fusiona por una puntuación. Los pesos y las bandas viven en un solo módulo, con el motivo de cada número; nada más los fija en el código.
Lo que mostró el conjunto de pruebas
El conjunto tiene 251 tareas sintéticas en ruso e inglés, con 80 pares duplicados. Usa el bm25() real
de FTS5 y el lematizador de cli-core; los pesos se ajustaron con una mitad y se probaron con la otra
(WireCatLabs/cli-testing#60).
- «Quizá» y «probable» juntas encuentran el 90% de los duplicados. El 58% de los pares que muestran son duplicados reales, y el 21% de las tareas nuevas se rechaza hasta que se elige.
- «Probable» sola acierta el 85% de las veces y encuentra el 43% de los duplicados.
- Ninguna puntuación es segura para fusionar sola. La primera puntuación sin fusiones erróneas es 16, y atrapa el 4% de los duplicados. Las tareas parecidas que quedan, como un regalo para mamá y otro para papá por el mismo importe con dos días de diferencia, se distinguen por el sentido de una palabra, que ningún campo ve.
Límites: los datos son sintéticos, la misma persona escribió los datos y la puntuación, y los duplicados entre idiomas se detectan mal (2 de 5 salieron «distinta»). Aún no hay medidas sobre chats reales.
6. Decidir
| Banda | Tarea añadida por un agente | Tarea abierta por una regla |
|---|---|---|
| Segura (paso 2) | no se aplica | nada nuevo |
| Probable | aparece primero; el agente debe elegir | a la lista de revisión |
| Quizá | tasks add se niega y lista hasta 5 candidatas con puntuación y motivos; el agente repite con --attach <task> o --new | a la lista de revisión |
| Distinta | se crea | se crea |
La herramienta se niega en lugar de avisar: un agente puede pasar por alto un aviso, pero no un código de salida. Preguntar al agente que crea la tarea no cuesta una llamada extra a un proveedor de IA, porque ese agente ya es el juez. Mem0 funciona igual: el agente que escribe un recuerdo elige añadirlo, actualizar uno existente, borrar uno o no hacer nada (Mem0, arXiv 2504.19413).
La lista de revisión. tasks duplicates lista los pares que una regla dejó en «probable» o «quizá»,
los más nuevos primero, con motivos. tasks merge <dup> --into <task> vincula todos los mensajes del
duplicado a la tarea que se queda, cierra el duplicado como dismissed con un vínculo duplicate-of
y registra la decisión. tasks duplicates reject <dup> <task> registra «no es lo mismo», así que el
par no vuelve a aparecer.
Una fusión se puede deshacer. tasks unmerge <dup> reabre el duplicado y quita el vínculo. Los
vínculos a mensajes se quedan donde están hasta que se muevan. Una fusión nunca borra nada.
7. Aprender de las respuestas
Cada fusión, rechazo, --attach y --new tras una lista de candidatas es un par etiquetado. Con
suficientes pares, m y u de cada nivel se vuelven a contar a partir de ellos, la misma estimación
por conteo que usa Splink, sin paso de entrenamiento, y las bandas se mueven. Una puntuación nunca
fusiona sola. Si las respuestas reales muestran que casi siempre aceptas los pares «probable»,
fusionarlos será decisión tuya.
Un ejemplo resuelto
Bob Sample escribe en un chat privado: «Can you send the invoice for 15 000 ₽ by Friday?» (¿me mandas la factura de 15 000 ₽ antes del viernes?). Una regla abre una petición. Antes de crearla, la comprobación encuentra dos candidatas de Bob Sample:
- REQ-22, creada ayer: «Send the invoice for 15000 ₽», categoría
money, sin fecha límite. - REQ-19, creada hace 10 días: «Send the contract invoice, 40 000 ₽», categoría
money, sin fecha límite.
Las raíces de la tarea nueva, con el lematizador inglés, son send, invoic, 15000, friday. En
este ejemplo los trigramas dan un valor más bajo que las raíces en los dos pares, así que cuentan las
raíces.
| Campo | REQ-22 | Puntos | REQ-19 | Puntos |
|---|---|---|---|---|
| Texto | raíces send, invoic, 15000: 3 comunes de 4 distintas = 0,75 | +5 | raíces send, contract, invoic, 40000: 2 comunes de 6 distintas = 0,33 | +5 |
| Persona | misma | +1 | misma | +1 |
| Importe | 15000 ₽ = 15000 ₽ | +6 | 15000 ₽ ≠ 40000 ₽ | −4 |
| Categoría | money = money | +1 | money = money | +1 |
| Fecha límite | falta en REQ-22 | 0 | falta en REQ-19 | 0 |
| Cadena de respuestas | no | 0 | no | 0 |
| Tiempo | 1 día | +2 | 10 días | −2 |
| Total | 15: probable | 1: distinta |
La petición la encontró una regla, así que la herramienta la crea como REQ-23 y pone el par REQ-23 y
REQ-22 en la lista de revisión. Nada se fusiona por la puntuación: tu agente o tú revisáis el par y
ejecutáis tasks merge REQ-23 --into REQ-22. Si la tarea la hubiera añadido un agente, tasks add se
negaría, mostraría REQ-22 primero con 15 puntos y «misma persona, mismo importe, 3 de 4 raíces en
común», y el agente repetiría con --attach REQ-22. REQ-19 comparte la persona y las palabras «send
invoice», pero el importe y la antigüedad la hunden: es otro trabajo.
Comparación con beads
beads es un gestor de incidencias para agentes de programación. Algunas de sus ideas se toman aquí y otras no.
| beads | Aquí |
|---|---|
Identificadores hash bd-a3f8 | Claves legibles de un contador: un almacén, un escritor cada vez |
| Etiquetas para todo lo que no es un campo | Una categoría de una lista fija, más las etiquetas que ya existen |
Tipos de arista como duplicates y discovered-from | duplicate-of y discovered-from se suman a los tipos de vínculo |
bd ready: trabajo abierto y sin bloqueos | tasks agenda, con filtros por categoría y etiqueta previstos |
bd update --claim: una reserva atómica | tasks claim, un préstamo que termina solo |
external_ref a un elemento externo | Cada tarea se vincula a todos sus mensajes |
bd duplicates fusiona copias exactas; bd find-duplicates puntúa la coincidencia de palabras y solo informa | Una comprobación antes de crear, pesos por campo y una elección del agente o una lista de revisión |
El texto de los chats nunca es una copia exacta, y nada en beads comprueba antes de crear una tarea. Por eso la comprobación de aquí trabaja por campos y pregunta cuando no está segura.
Trabajos relacionados
Los informes de errores duplicados son un problema antiguo. REP puntúa dos informes por su texto y por campos como el producto y el componente, con pesos ajustados sobre duplicados conocidos (Sun et al., ASE 2011). La comprobación de aquí usa la misma idea de texto más campos, con pesos que se cuentan, no que se entrenan.
Lo que no se hace
- Ni embeddings ni llamadas a un proveedor de IA para comparar tareas. Si el conjunto de pruebas muestra que el campo de texto es demasiado débil, el siguiente paso es un embedding local, medido frente a esta comprobación.
- Nada se borra. Un duplicado se cierra y se vincula; nunca se elimina.
- Ningún límite de una tarea por chat o por persona.
- Ningún identificador hash, hasta que los almacenes se sincronicen entre dispositivos.
- Preguntas abiertas: la lista de categorías por defecto, y si un mensaje que coincide con una
tarea cerrada en los últimos 14 días la reabre o abre una tarea nueva vinculada con
follow-up-of.