Titentiten.devꦠꦶꦠꦺꦤ꧀
v0.1.1 · npmGitHub
DocsCollaborationCheckpoints

Checkpoints

Resumable task state for one agent, keyed on subject and kind, with a TTL that makes it expire — deliberately kept out of the evidence store.

task_state · conversation · workflow · cursorTTL 60s – 30dstate ≤ 64 kB

State, not facts

A checkpoint answers “where did I get to?”. An observation answers “what happened?”. Those are different records with different lifetimes, and merging them is how a memory store fills up with stale scaffolding that later reads as truth.

The security invariant is explicit: checkpoints, leases and handoffs are execution state, never factual evidence. Checkpoint text alone cannot support a claim, because consolidation only accepts observation ids as sources. If an agent finishes work and the finding matters, it appends an observation. The checkpoint is what it throws away.

Checkpoints expire. Evidence does not.

A checkpoint is overwritten in place. An observation is append-only.

A checkpoint cannot be cited as a source. Only observations can.

Save, and save again

There is no separate create-or-update decision. POST /v1/checkpoints upserts on organization, subject, agent and kind, so the second write returns 200 with updated: true and the same checkpoint_id.

resume.tstypescript
const saved = await titen.saveCheckpoint({
  subject_id: 'user_rama',
  kind: 'task_state',
  state: { step: 3, of: 7, pending: ['rollback smoke'] },
  ttl_seconds: 3600,
});
// saved.checkpoint_id → "ckpt_713be19162544650a85b76b5ef6d7543"
// saved.updated       → false, then true on every later write

The response carries a state_hash rather than echoing the payload back, which is enough to tell whether a write changed anything:

{ "data": { "checkpoint_id": "ckpt_713be19162544650a85b76b5ef6d7543",
    "subject_id": "user_rama", "agent_id": "agent_8df7b8699a0945488ceb3d4b104006d6",
    "kind": "task_state",
    "state_hash": "5ab982ea637562b9b4d1c752bf073346eb27d7ea673a2adb463c1324c2fe947b",
    "expires_at": "2026-07-30T11:48:04.652Z", "updated": true } }

kind is one of task_state, conversation, workflow or cursor — a closed set, so the lookup key stays predictable across agents. state is any JSON value except null, under 64 000 bytes serialized. ttl_seconds runs from 60 to 2 592 000 (30 days). All three are checked before the write; a bad kind comes back as:

{ "error": { "code": "VALIDATION_ERROR",
    "message": "Field \"kind\" must be one of: task_state, conversation, workflow, cursor." } }

Reading one back

GET /v1/checkpoints is a lookup, not a list. It requires subject_id and kind, and agent_id defaults to the calling credential’s own principal.

curl -H "authorization: Bearer $TITEN_API_KEY" \
  'http://127.0.0.1:8787/v1/checkpoints?subject_id=user_rama&kind=task_state'

The response is the full record, including the parsed state, ttl_seconds, expires_at, created_at and updated_at. Anything missing, expired, or belonging to another agent returns the same non-disclosing 404:

{ "error": { "code": "NOT_FOUND", "message": "Resource was not found." } }
PER-AGENT BY DEFAULT

Because agent_id defaults to the caller, two agents saving task_state for the same subject get two independent checkpoints and neither can read the other’s by accident. Pass agent_id explicitly to resume someone else’s work — which is what a handoff hands you.

Expiry is enforced at read time: the lookup carries an expires_at > now predicate, so an expired checkpoint stops being visible without anything having to sweep it. Nothing in the background deletes checkpoint rows. DELETE /v1/checkpoints/:id is the explicit removal and returns { "checkpoint_id": "…", "deleted": true }.

Checkpoints and compiled context

POST /v1/context/compile accepts include_checkpoints: true, and today it answers honestly that it cannot do it:

{ "meta": { "degraded": { "semantic": false, "vector": "disabled",
      "model": "disabled", "checkpoints": "unavailable" }, "candidates": 1 } }

Checkpoints do not enter a compiled pack yet. Fetch the one you need with getCheckpoint and put it in your prompt as a separate block — which is the honest shape anyway, since task state and retrieved evidence should not arrive looking alike. The flag exists so the request survives when the capability lands; meta.degraded is the contract that tells you it has not.

From MCP

Two of the seven MCP tools are checkpoints, so an agent that speaks only MCP can still resume: titen_checkpoint_save takes subject_id, kind, state and ttl_seconds, and titen_checkpoint_get takes subject_id and kind. Both arrive over POST /mcp, which takes the same bearer key as REST and is gated on a single scope, mcp:call.

Next

↑↓ navigate↵ open