Audit
The audit is a table, organtia.auditoria, with one row for every operation that writes or tries
to. It is not a conversation diary or an access log: it is the list of what was done, who asked
for it and with what result.
What gets recorded
Section titled “What gets recorded”A row is written every time a tool or a screen modifies data:
| Source | Examples of herramienta |
|---|---|
| The CEO’s tools | recordar_hecho, crear_objetivo, actualizar_objetivo, registrar_decision, solicitar_puesto, solicitar_recurso, guardar_plan_estrategico, proponer_campana, crear_pieza_comunicacion, otorgar_acceso, revocar_acceso, marcar_hecho_obsoleto |
| Approvals from the CLI | cli:puesto_activo, cli:puesto_rechazado, cli:decision_aprobada, cli:solicitud_aprobado, cli:aprobar_plan, cli:conceder_autonomia, cli:revocar_autonomia, cli:crear_proyecto, cli:crear_tarifa, cli:crear_canal, cli:registrar_suscriptor, cli:directrices_puesto, cli:puesto_cesado |
| Office screens | oficina:puesto_editado, oficina:puesto_desde_perfil, oficina:puesto_ajustado, oficina:recurso_puesto, oficina:recurso_borrado, oficina:memoria_puesto, oficina:memoria_borrada |
| Settings | ui:instrucciones, every time the CEO’s instructions are saved or restored |
And a row with resultado = 'error' when one of those operations fails. The failure is written in
a separate transaction: if the audit cannot be written, the original error is neither hidden nor
turned into a different one.
What does not get recorded
Section titled “What does not get recorded”- Reads. Querying memory, listing roles, opening a profile page: none of that leaves a row.
- Sign-ins and sign-outs at the gate. A successful login leaves a line in the process log
(
Entrada en la oficina: pedro) and updatesacceso_usuarios.ultimo_acceso, but it does not write to the audit. - Conversations. Messages and replies live in
agno.agno_runs. The audit keeps what was written to the business tables, not the text that was spoken. - Failures at the model provider. A network outage or an exhausted quota does not generate a row.
Three tables look like this one and are not:
| Table | What it stores |
|---|---|
organtia.conexion_ejecuciones |
Every call to an external application (connector): action, URL, HTTP code, duration, error. |
organtia.ejecuciones |
Orders to run a project’s tests, with their output and exit code. |
agno.agno_runs |
Each agent’s conversation turns, with their token metrics. |
The fields
Section titled “The fields”| Field | Type | What it means |
|---|---|---|
id |
bigserial | Operation number, in insertion order. |
ocurrido_en |
timestamptz | When it was recorded, with time zone. The CLI shows it trimmed to 19 characters (2026-11-06 09:12:33). |
actor |
text | Who asked for it, according to what the code knows about them. adrian by default. |
herramienta |
text | What the operation was. It is the tool’s name or the screen’s prefix. |
entidad |
text | What kind of thing was acted on: hecho, objetivo, puesto, proyecto, plan, decision, solicitud_recurso, campana, pieza_comunicacion, tarifa, canal, suscriptor, acceso, autonomia, instrucciones, puesto_recurso, puesto_memoria… |
entidad_id |
text | The identifier of that thing (the id of the row affected), as text. |
resultado |
text | ok or error. The database accepts no other value. |
detalle |
jsonb | What it takes to understand the operation. See below. |
sesion_id |
text | The session it was done from, if there was one. It can be null. |
actor: who
Section titled “actor: who”The value is not an authenticated identity in every case; it is what the code attributes:
| Value | When it appears |
|---|---|
adrian |
Operations from the CEO’s tools. It is the default value of the table and of the repository. |
The owner’s name (OWNER_NAME, Pedro by default) |
Edits made from the office and approvals made by name: role pages, resources, memory, projects, rates. |
propietario |
Only the saving of the CEO’s instructions (ui:instrucciones). |
sesion_id: from where
Section titled “sesion_id: from where”| Value | Meaning |
|---|---|
api:<usuario> |
HTTP request made by that person through the gate (for example api:pedro). |
| An identifier declared by the client | The test runner sends its own in the X-Sesion header; it is cleaned and trimmed to 80 characters. |
servicio:reloj |
Call authenticated with INTERNAL_SERVICE_TOKEN by the cron clock. |
cron:<name> |
Turn launched by a cron job; the name is the cron job’s. |
| CLI session | The identifier of the conversation session open in the terminal. |
null |
Operations that do not come from a conversation: saving instructions, editing a page. |
detalle: the content
Section titled “detalle: the content”In a successful write, the object carries proof that what was written was read back:
{ "verificado": true, "campos_verificados": ["estado", "titulo"], "huella": {"contenido": "9f2c..."}}verificadois alwaystrueinokrows: the repository inserts, reads back and only then confirms.campos_verificadoslists the fields that were compared with what was sent.huellais the sha256 of the long text fields (over 40 characters), trimmed to its first 16 hexadecimal characters, so you can check the content did not change without storing the whole text in the log.
In an error row the detail is shorter and carries the message, trimmed to 1000 characters:
{"error": "ErrorVerificacion: el valor releído de «estado» no coincide con el enviado"}Some operations add their own keys to the detail. Do not count on a fixed shape: look at the keys before reading a specific field.
Where it is queried
Section titled “Where it is queried”In the CLI, which is the intended route:
/auditoria # last 15/auditoria 50 # last 50The table it prints has five columns: fecha, actor, herramienta, resultado and id (the
last one is entidad_id, or — if there is none). The maximum it returns is 200 rows, even if
you ask for more.
In the database, read-only:
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c " SELECT ocurrido_en, actor, herramienta, entidad, entidad_id, resultado FROM organtia.auditoria ORDER BY ocurrido_en DESC LIMIT 20"'Queries that usually come in handy:
-- Everything that happened in a specific sessionSELECT ocurrido_en, herramienta, resultado, detalle FROM organtia.auditoria WHERE sesion_id = 'api:pedro' ORDER BY ocurrido_en DESC;
-- Only the failures of the last seven daysSELECT ocurrido_en, actor, herramienta, detalle->>'error' AS error FROM organtia.auditoria WHERE resultado = 'error' AND ocurrido_en > now() - interval '7 days' ORDER BY ocurrido_en DESC;
-- What was touched on a roleSELECT ocurrido_en, herramienta, actor FROM organtia.auditoria WHERE entidad = 'puesto' AND entidad_id = '12' ORDER BY ocurrido_en DESC;There is no web view of the audit. The office menu has no screen for this: you query it through the CLI or through SQL.
Immutability and retention
Section titled “Immutability and retention”The application role (APP_DB_USER, organtia_app by default) has SELECT and INSERT on the
table, and UPDATE, DELETE and TRUNCATE are revoked. That is: the application itself cannot
rewrite or delete what it already recorded, not even through a programming error.
There is no automatic purge and no configurable retention: the table grows and does not empty itself. In a heavily used installation it is one of the heaviest; if it ever has to be trimmed, it is done by hand and with a backup first.
How to read a row
Section titled “How to read a row”A full example, and what each part says:
2026-11-06 09:12:33 | Pedro | oficina:puesto_editado | ok | 12- It happened on 6 November 2026 at 09:12:33, server time.
- The owner asked for it from the office.
- It was an edit to a role page.
- It went through and the result was read back.
- The role affected is
12.
If that same row said error, the detail would carry the message and you would know the page was
not saved, even if the interface had shown the notice differently.
