CLOCK — API HTTP

CLOCK — Referência da API HTTP

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.


Sumário


Como rodar

cargo 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.


Convenções

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)" }

Conceitos

Canais (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)

Classes de volatilidade (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

Decisão de leitura (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)

Decomposição da confiança (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]

Tipos de evento (event_type)

Como uma observação foi classificada na escrita: assertion · recitation · independent_confirmation · contradiction.


Endpoints

POST /ingest

Mensagem 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" }

roleuser | agent | tool. Mapeia para o canal: useruser, agentmemory_self (re-citação — nunca confirma), tooltool. Só o usuário e ferramentas podem criar/confirmar crenças; o agente não pode “inventar” memória.

ResponseIngestOutcome:

{
  "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 /query

Consulta 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).

RequestQueryRequest:

{ "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.

ResponseQueryResult:

{
  "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 /chat

Um 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 */ ]
}

POST /advance

Avanç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 /reset

Limpa 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 /graph

O 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/cql

Consulta 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" }

ResponseCypherResult:

{ "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/cypher

Mesmo contrato do CQL, mas com um subconjunto de Cypher.

Request: { "query": "MATCH (a)-[r]->(b) RETURN a, r, b" }

ResponseCypherResult (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 /health

Sonda de saúde + contagem de eventos no ledger.

Response: { "status": "ok", "events": 0 }


POST /observe

Registra 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.

RequestObservation:

{
  "subject": "user:42", "predicate": "employer", "value": "Acme",
  "channel": "user", "volatility_class": "employer", "intrinsic_confidence": 0.9
}

subject, predicate, value, channel são obrigatórios. Opcionais: source_id, root, prompted_by_premise, volatility_class, intrinsic_confidence, note.

ResponseObserveOutcome:

{
  "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 /recall

Recupera 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).

ResponseRecallResult:

{
  "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 /belief

A 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_id

O 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-00a1ba22f3ea

ResponseLedgerEvent[]:

[
  {
    "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
  }
]

Tipos de dados

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

RecallDecision

Enum com tag kind (snake_case): - { "kind": "use" } - { "kind": "use_with_hedge", "hedge": "…" } - { "kind": "verify", "micro_question": "…" } - { "kind": "not_found" }

ConfidenceBreakdown

age_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" }