API JSON sobre o sistema de memória nativo do CLOCK (crate
clock-server). O servidor também serve uma UI web (chat +
grafo) em GET /.
A inversão que rege tudo: a confiança de uma crença decai pelo tempo desde a última confirmação independente — nunca desde o último uso. Re-citar um fato é evidência nula. A API expõe essa máquina: você observa fatos, consulta com confiança calibrada por risco, e o tempo (real ou simulado via
/advance) corrói o que não é reconfirmado.
/ingest · /query · /chat/advance · /reset/graph · /graph/cql · /graph/cypher/health · /observe · /recall · /belief · /ledger/:idcargo run -p clock-server
# CLOCK memory server on http://127.0.0.1:8088 (dados: clock-data/)Variáveis de ambiente (todas opcionais; lidas também de um
.env):
| Variável | Default | Função |
|---|---|---|
CLOCK_ADDR |
127.0.0.1:8088 |
endereço de bind |
CLOCK_DATA |
clock-data |
diretório do log append-only (a memória persistida) |
CLOCK_CHAT_MIN_SIM |
0.35 |
piso de similaridade pra um fato entrar no contexto do agente no
/chat |
AZURE_OPENAI_ENDPOINT |
— | recurso Azure OpenAI (ex.:
https://x.openai.azure.com) |
AZURE_OPENAI_API_KEY |
— | chave do recurso |
AZURE_OPENAI_API_VERSION |
2024-10-21 |
versão da API |
AZURE_OPENAI_CHAT_DEPLOYMENT |
— | deployment GPT-4.1 (extração de fatos + resposta do chat) |
AZURE_OPENAI_EMBED_DEPLOYMENT |
— | deployment de embeddings |
AZURE_OPENAI_EMBED_DIM |
1536 |
dimensão do embedding |
Modo degradado: sem as variáveis
AZURE_OPENAI_*, o servidor sobe mesmo assim, mas sem
extração de fatos (/ingest e /chat não
aprendem nada do texto), com embeddings por hash (busca semântica fraca)
e sem resposta do agente. O motor de crenças (/observe,
/recall, grafo, etc.) funciona normalmente.
http://127.0.0.1:8088Content-Type: application/json.{ "error": "mensagem" }.| Status | Quando |
|---|---|
200 |
sucesso |
400 |
observação inválida, classe de volatilidade desconhecida, query (CQL/Cypher) com sintaxe inválida |
404 |
crença inexistente em GET /belief |
502 |
falha numa chamada ao Azure OpenAI (extração/embedding/chat) |
500 |
erro interno (I/O do ledger, panic) |
Exemplo de erro:
curl -s "http://127.0.0.1:8088/belief?subject=nope&predicate=nope"
# HTTP 404
{ "error": "no active belief for (subject, predicate)" }channel)De onde veio a observação determina se ela pode ser independente (resetar o relógio de decaimento):
| Canal | Pode ser independente? | Significado |
|---|---|---|
user |
sim | o usuário (ou a parte modelada) afirmou |
tool |
sim | uma ferramenta read-only/idempotente retornou o valor |
derived_fact |
sim | outro fato armazenado, de origem distinta, implica este |
memory_self |
não | o agente reusou a própria memória — re-citação |
echo_telemetry |
não | sinal comportamental atribuído à crença (evidência de fricção, não de validade) |
volatility_class)Cada classe tem uma curva de sobrevivência (Weibull) com mediana e
formato próprios. Definidas no /observe (ou escolhidas pelo
extrator no /ingest):
immutable · employer ·
job_title · home_address · city ·
phone_number · email ·
marital_status · subscription ·
preference · allergy · mood ·
default
immutable nunca decai (sobrevivência
sempre 1.0): nome, data de nascimento, parentesco. No
/ingest, predicados como
name/filho/pai/mae
são forçados a essa classe.default é o fallback pra qualquer predicado não
classificado.decision)Toda recuperação devolve uma decisão calibrada por risco
(risk):
kind |
Significado | Campo extra |
|---|---|---|
use |
confiança acima do limiar — use direto | — |
use_with_hedge |
abaixo do limiar, baixo risco — use com ressalva | hedge (texto) |
verify |
abaixo do limiar, alto risco — confirme antes | micro_question (texto) |
not_found |
não há crença ativa pra (subject, predicate) |
— |
confidence)effective = survival × intrinsic × silence, tudo
auditável:
| Campo | O quê |
|---|---|
age_days |
dias desde a última confirmação independente |
survival |
termo de sobrevivência S_class(age) — 1.0
pra immutable |
intrinsic |
força da evidência original |
silence |
modulador de silêncio suspeito ∈ (0, 1] |
effective |
produto final, em [0,1] |
event_type)Como uma observação foi classificada na escrita:
assertion · recitation ·
independent_confirmation · contradiction.
POST /ingestMensagem em linguagem natural → extrai fatos (subject, predicate,
value) e os grava. Requer Azure pra extrair; em modo
degradado grava a mensagem e retorna facts: [].
Request — corpo é um IngestMessage:
{ "session_id": "s1", "subject": "user:42", "role": "user", "text": "trabalho na Acme" }role ∈ user | agent |
tool. Mapeia para o canal:
user→user,
agent→memory_self (re-citação — nunca
confirma), tool→tool. Só o usuário e
ferramentas podem criar/confirmar crenças; o agente não pode “inventar”
memória.
Response — IngestOutcome:
{
"message_id": 1,
"facts": [
{
"subject": "user:42", "predicate": "employer", "value": "Acme",
"event_type": "assertion", "belief_id": "8b09…", "embedded": true,
"skipped": false, "reason": "no prior active belief for (subject, predicate)"
}
]
}curl -s -X POST http://127.0.0.1:8088/ingest -H 'content-type: application/json' \
-d '{"session_id":"s1","subject":"user:42","role":"user","text":"trabalho na Acme"}'POST /queryConsulta semântica: devolve a janela de conversa, os fatos ranqueados por similaridade (cada um com confiança e decisão) e os pontos a revalidar (micro-confirmações pra fatos obsoletos).
Request — QueryRequest:
{ "session_id": "s1", "text": "onde a pessoa trabalha?", "top_k": 5, "window": 6, "risk": "high" }top_k (default 5), window (nº de mensagens
recentes, default 10) e risk (low |
high, default high) são opcionais.
Response — QueryResult:
{
"window": [
{ "id": 1, "session_id": "s1", "role": "user", "text": "trabalho na Acme", "timestamp": 1781017584 }
],
"facts": [
{
"subject": "user:42", "predicate": "employer", "value": "Acme",
"similarity": 0.81,
"confidence": { "age_days": 0.0, "survival": 1.0, "intrinsic": 0.9, "silence": 1.0, "effective": 0.9 },
"decision": { "kind": "use" }
}
],
"revalidate": [
{
"subject": "user:42", "predicate": "plan", "value": "Pro", "confidence": 0.38,
"prompt": "Revalide com o usuário: \"plan\" de user:42 ainda é \"Pro\"? (confiança 38%, possivelmente desatualizado — confirme antes de usar)"
}
]
}revalidate só lista fatos cuja decisão veio
verify (obsoletos, sob risco alto). Fatos use
ou immutable nunca aparecem ali.
curl -s -X POST http://127.0.0.1:8088/query -H 'content-type: application/json' \
-d '{"session_id":"s1","text":"emprego","top_k":5,"window":6,"risk":"high"}'POST /chatUm turno de chat com o agente, usado pela UI web. Recupera fatos, monta o system prompt com eles, responde via GPT-4.1, e ingere a mensagem do usuário. Requer Azure pra responder.
Request:
{ "session_id": "web", "subject": "user", "text": "oi", "system": "" }subject (default = session_id) e
system (persona editável; vazio → persona padrão) são
opcionais. O CLOCK sempre anexa os fatos recuperados ao
system.
Response:
{
"reply": "…resposta do agente…",
"recalled": [ { "predicate": "employer", "value": "Acme", "effective": 0.9, "decision": "use", "similarity": 0.81 } ],
"query": [ /* mesmos campos de recalled, mas o top-k completo sem filtro de similaridade */ ],
"learned": [ { "predicate": "employer", "value": "Acme", "event_type": "Assertion" } ],
"window": [ { "role": "user", "text": "oi" } ],
"revalidate": [ /* Revalidation[], igual ao /query */ ]
}recalled: fatos acima de
CLOCK_CHAT_MIN_SIM — o que o agente de fato usou.query: o top-k completo (sem filtro) — pro painel de
consulta.learned: fatos extraídos neste turno.POST /advanceAvança o relógio do servidor em N dias — ferramenta de teste pra ver o decaimento sem esperar tempo real. O offset é de sessão (zera ao reiniciar).
Request: { "days": 400 } →
Response: { "offset_days": 400.0 }
curl -s -X POST http://127.0.0.1:8088/advance -H 'content-type: application/json' -d '{"days":400}'POST /resetLimpa memória. Três modos (avaliados nesta ordem):
| Corpo | Efeito |
|---|---|
{ "session_id": "s1", "conversation": true } |
apaga só a janela de conversa da sessão; crenças/grafo intactos |
{ "all": true } |
apaga tudo (todos os subjects/sessões) |
{ "session_id": "s1", "subject": "user:42" } |
apaga a “thread”: crenças do subject + conversa da sessão |
Response: { "status": "ok" }
curl -s -X POST http://127.0.0.1:8088/reset -H 'content-type: application/json' \
-d '{"session_id":"s1","conversation":true}'GET /graphO grafo de conhecimento atual: nós (entidades) e arestas (crenças ativas, com confiança/decisão decaídas no tempo).
Response — { nodes:Node[], edges:Edge[] }:
{
"nodes": [
{ "id": 0, "key": "user:42", "label": "user:42", "out_degree": 1, "in_degree": 0 },
{ "id": 1, "key": "Acme", "label": "Acme", "out_degree": 0, "in_degree": 1 }
],
"edges": [
{ "id": 0, "type": "employer", "src": 0, "dst": 1, "belief_id": "8b09…",
"value": "Acme", "confidence": 0.9, "decision": "use", "age_days": 0.0 }
]
}src/dst referenciam
Node.id.
POST /graph/cqlConsulta o grafo em CQL (CLOCK Query Language) —
linguagem nativa, com caminhos nó -rel-> nó e condições
de primeira classe como stale, fresh,
confidence, age, decision.
Request:
{ "query": "?a -employer-> ?b" }
Response — CypherResult:
{ "columns": ["a","b"], "rows": [ [ {"kind":"node","key":"user:42"}, {"kind":"node","key":"Acme"} ] ] }Exemplos: ?a -employer-> ?b where stale ·
ana -employer-> ?x ·
?a -?r-> ?b where confidence < 0.5.
POST /graph/cypherMesmo contrato do CQL, mas com um subconjunto de Cypher.
Request:
{ "query": "MATCH (a)-[r]->(b) RETURN a, r, b" }
Response — CypherResult (células
node e edge):
{
"columns": ["a","r","b"],
"rows": [[
{ "kind": "node", "key": "user:42" },
{ "kind": "edge", "type": "employer", "value": "Acme", "confidence": 0.9, "decision": "use", "age_days": 0.0 },
{ "kind": "node", "key": "Acme" }
]]
}Query inválida → 400 com
{ "error": "expected Match, got …" }.
GET /healthSonda de saúde + contagem de eventos no ledger.
Response:
{ "status": "ok", "events": 0 }
POST /observeRegistra uma observação no motor de crenças (baixo nível, sem LLM). É o ponto de entrada direto: classifica o evento (assertion / confirmation / contradiction / recitation), atualiza a crença e o relógio de decaimento.
Request — Observation:
{
"subject": "user:42", "predicate": "employer", "value": "Acme",
"channel": "user", "volatility_class": "employer", "intrinsic_confidence": 0.9
}Só subject, predicate, value,
channel são obrigatórios. Opcionais:
source_id, root,
prompted_by_premise, volatility_class,
intrinsic_confidence, note.
Response — ObserveOutcome:
{
"event_type": "assertion",
"belief_id": "8b096477-b508-4d23-b595-00a1ba22f3ea",
"reason": "no prior active belief for (subject, predicate)",
"superseded": null
}superseded traz o belief_id antigo quando
há contradição.
curl -s -X POST http://127.0.0.1:8088/observe -H 'content-type: application/json' \
-d '{"subject":"user:42","predicate":"employer","value":"Acme","channel":"user","volatility_class":"employer"}'POST /recallRecupera uma crença específica (subject, predicate) com
confiança decaída e decisão calibrada por risco.
Request:
{ "subject": "user:42", "predicate": "employer", "risk": "high" }
(risk opcional, default high).
Response — RecallResult:
{
"belief": { /* Belief completo, ver Tipos */ },
"confidence": { "age_days": 0.0, "survival": 1.0, "intrinsic": 0.9, "silence": 1.0, "effective": 0.9 },
"decision": { "kind": "use" }
}Quando não há crença ativa: confidence: null e
decision: { "kind": "not_found" }.
GET /beliefA crença ativa de (subject, predicate) + sua confiança
atual. Parâmetros via query string.
curl -s "http://127.0.0.1:8088/belief?subject=user:42&predicate=employer"Response — { belief:Belief, confidence:ConfidenceBreakdown| null }.
404 se não existir crença ativa.
GET /ledger/:belief_idO ledger append-only completo de uma crença — todos os eventos imutáveis que a formaram, em ordem. É a auditoria: dá pra reconstruir a crença inteira a partir daqui.
curl -s http://127.0.0.1:8088/ledger/8b096477-b508-4d23-b595-00a1ba22f3eaResponse — LedgerEvent[]:
[
{
"id": 5, "belief_id": "60a4…", "subject": "user:42", "predicate": "employer",
"event_type": "assertion", "value": "Acme",
"provenance": { "channel": "user", "source_id": "", "root": "7597…" },
"timestamp": 1781017584, "prompted_by_premise": false,
"volatility_class": "employer", "intrinsic_confidence": 0.9, "note": null
}
]Timestamps são Unix em segundos (inteiros).
IngestMessage| Campo | Tipo | Notas |
|---|---|---|
session_id |
string | obrigatório |
subject |
string | default = session_id |
role |
user|agent|tool |
obrigatório |
text |
string | obrigatório |
IngestOutcome| Campo | Tipo |
|---|---|
message_id |
int |
facts |
FactEvent[] |
FactEvent| Campo | Tipo | Notas |
|---|---|---|
subject, predicate,
value |
string | |
event_type |
string | null | assertion/recitation/independent_confirmation/contradiction |
belief_id |
string | null | |
embedded |
bool | se gerou embedding |
skipped |
bool | true quando o fato foi descartado (ex.: agente tentando inventar crença) |
reason |
string | explicação da classificação/descarte |
QueryRequest| Campo | Tipo | Default |
|---|---|---|
session_id |
string | — |
text |
string | — |
top_k |
int | 5 |
window |
int | 10 |
risk |
low|high |
high |
QueryResult| Campo | Tipo |
|---|---|
window |
ChatMessage[] (id,
session_id, role, text,
timestamp) |
facts |
RecalledFact[] |
revalidate |
Revalidation[] |
RecalledFact| Campo | Tipo |
|---|---|
subject, predicate,
value |
string |
similarity |
float (cosseno) |
confidence |
ConfidenceBreakdown
| null |
decision |
RecallDecision |
Revalidation| Campo | Tipo |
|---|---|
subject, predicate,
value |
string |
confidence |
float (efetiva) |
prompt |
string — micro-confirmação pronta pro agente usar |
Observation| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
subject, predicate,
value |
string | sim | |
channel |
Channel |
sim | user/tool/derived_fact/memory_self/echo_telemetry |
source_id |
string | não | id concreto da fonte |
root |
string | null | não | raiz de independência explícita |
prompted_by_premise |
bool | não | true = reafirmação ecoada (não reseta o relógio) |
volatility_class |
string | null | não | só para crença nova |
intrinsic_confidence |
float | null | não | só para crença nova |
note |
string | null | não |
ObserveOutcome| Campo | Tipo |
|---|---|
event_type |
string |
belief_id |
string |
reason |
string |
superseded |
string | null (belief_id substituído numa contradição) |
RecallResult| Campo | Tipo |
|---|---|
belief |
Belief | null |
confidence |
ConfidenceBreakdown
| null |
decision |
RecallDecision |
RecallDecisionEnum com tag kind (snake_case): -
{ "kind": "use" } -
{ "kind": "use_with_hedge", "hedge": "…" } -
{ "kind": "verify", "micro_question": "…" } -
{ "kind": "not_found" }
ConfidenceBreakdownage_days, survival, intrinsic,
silence, effective — todos float. Ver Conceitos.
Belief| Campo | Tipo |
|---|---|
id |
string (uuid) |
subject, predicate,
value |
string |
volatility_class |
string |
intrinsic_confidence |
float |
t_created,
t_last_independent_confirmation |
timestamp |
recitation_count,
independent_confirmation_count,
contradiction_count |
int |
status |
active | contested |
primary_root |
string |
independent_roots |
string[] |
valid_from |
timestamp |
valid_to |
timestamp | null (bi-temporal: preenchido quando a crença é substituída) |
LedgerEvent| Campo | Tipo |
|---|---|
id |
int (sequencial no log) |
belief_id, subject,
predicate, value |
string |
event_type |
string |
provenance |
{ channel, source_id, root } |
timestamp |
timestamp |
prompted_by_premise |
bool |
volatility_class |
string |
intrinsic_confidence |
float |
note |
string | null |
Node{ id: int, key: string, label: string, out_degree: int, in_degree: int }
Edge{ id, type, src, dst, belief_id, value, confidence, decision, age_days }
— src/dst são Node.id;
confidence/decision/age_days são
decaídos no instante da consulta.
CypherResult{ columns: string[], rows: Cell[][] }. Cada
Cell é: - { "kind": "node", "key": "…" } -
{ "kind": "edge", "type", "value", "confidence", "decision", "age_days" }
- { "kind": "null" }