Extraction API

The Extraction API converts an agent's tool-call trace into a replayable procedure. The parse is deterministic and model-free: the same trace gives the same steps, in the same order, with the same classification. Two small models sit beside it, both inside Memorable's own account: one writes the title, and the second stage of the judge scores whether a workflow is kept. Neither can rename, reorder or edit a step. The title is copied into the text that gets embedded, so that one field is not byte-stable.

The API is not stateless. A key that belongs to a workspace writes one row per prompt, carrying the prompt text, and one row per admitted procedure, carrying the title, the steps and the conditions, because the dashboard renders them from the server. The corpus is never sent and never stored, and request bodies are never logged. The procedure is also written into your own database, on your side, through your existing GBrain connection.

agent prompt, everything an integrating agent needs
You are integrating a harness or agent with the Memorable Extraction API.
Base URL: https://memorable-extraction-api.memorable.workers.dev

1. Get a key once: keys belong to a workspace, so a human has to sign in.
   POST /v1/device/code with { "hostname": "..." } -> { user_code, verification_uri,
   device_code, expires_in, interval }. Show the user the code and the URL, then poll
   POST /v1/device/token with { device_code } every `interval` seconds until it returns
   { "status": "approved", "api_key": "mk_..." }. STOP and ask your human to approve it;
   you cannot complete a browser flow. Store the key; send it as
   "Authorization: Bearer mk_...". POST /v1/keys is closed and returns 403.
2. After each finished session, POST /v1/extract with:
   { "session_id": "<stable id>", "harness": "<your harness name, any string>",
     "task_description": "<one line: what the task was>", "skip_embedding": true,
     "tool_calls": [ { "name": "<tool>", "input": { "command"|"file_path"|"path"|
       "pattern"|"url"|"query": "<string>" }, "result": { "ok": true|false } |
       { "exit_code": 0 } } ] }
   Send ONLY these fields. Do not send conversation text, file contents, or
   credentials; include "result" only when the outcome is actually known.
3. Response: { "draft": { title, steps[{seq, action, activity_class, command?,
   repeat_count, targets?, creates?}], trigger_signature{entities, search_text}, preconditions,
   postconditions, ... }, "request_id": "..." }. The parse is deterministic and
   model-free, so identical input yields identical steps; the title alone is
   written by a small model and is not byte-stable. Store the draft yourself, on
   the user's side. The service keeps the task_description line and the extracted
   steps so the dashboard can render them; it does not keep the corpus.
4. Past the monthly allowance the API REFUSES. Still 200, still carrying the
   draft, plus refused: "allowance_exhausted" and a detail sentence. Nothing is
   stored on either side: check for `refused` before you store, and do not retry.
5. Errors: 401 unauthorized, 400 invalid_json/invalid_request, 413 over 8MB,
   429 rate_limited (300/min per key). Every response carries request_id;
   include it when reporting problems.

Base URL

https://memorable-extraction-api.memorable.workers.dev

Authentication

Every request requires a bearer token in the Authorization header. Requests without a valid token return 401. Keys look like mk_… and are issued through the device flow above. The key itself is never stored: only a SHA-256 hash of it is.

Authorization header
Authorization: Bearer $MEMORABLE_API_KEY

Every response carries a request_id (also in the x-memorable-request-id header). Include it when reporting a problem, it lets us find your exact request.

Get a key

post/v1/device/code

Keys belong to a workspace, so getting one means signing in. The CLI does it for you: memorable login starts a device authorization (RFC 8628), prints a short code, waits while you approve it in a browser, and saves the key to ~/.memorable/config.json. That flow suits the machines that most need a key, a container, an SSH session, a CI runner, because it needs no reachable localhost.

POST /v1/keys used to issue anonymous keys and now returns 403. A key with no owner cannot be metered, revoked, or shown to the person it belongs to.

Request
curl -X POST https://memorable-extraction-api.memorable.workers.dev/v1/device/code \
  -H "Content-Type: application/json" -d '{"hostname":"my-laptop"}'

# → { "user_code": "TN9G-VTQX", "verification_uri": "https://memorable.sh/dash/device",
#     "device_code": "…", "expires_in": 600, "interval": 5 }
# then poll POST /v1/device/token with { "device_code": "…" } until it returns
# { "status": "approved", "api_key": "mk_…" }

Create a procedure

post/v1/extract

Converts a trace into a ProcedureDraft. Any harness is accepted: known harnesses (claude-code, codex, opencode) get curated activity registries; every other harness string is served by a generic tier that still infers execution from command-shaped input.

Body parameters

session_idstringrequired
Stable identifier for the session. Re-extracting the same session updates the same stored procedure rather than creating a duplicate.
tool_callsarray of ToolCallRecordrequired
The ordered tool calls the agent made. Each record:
namestringrequired
Tool name exactly as the harness emitted it.
inputobjectrequired
The tool's raw arguments, unmodified. Never summarized or rewritten.
resultobjectoptional
Structured outcome, when available: { ok?: boolean, exit_code?: number }. Powers postcondition detection, success is derived from real outcomes, never guessed from a command's name. Omitting it is valid; postconditions simply come back empty.
task_descriptionstringoptional
One-line description of the task. When present it becomes the procedure's title and primary matching text.
corpusstringoptional
Free-text transcript, if the agent keeps one. Cloud agents with only a structured trace omit this entirely.
harnessstringoptional
Which agent produced the trace. Any string is accepted.
skip_embeddingbooleanoptional
Skip the server-side embedding call because you embed locally with your own provider. The CLI sets this automatically when your GBrain has an embedding provider configured.

Response fields

draft.titlestringrequired
The procedure's title, from task_description or the first meaningful transcript line.
draft.stepsarrayrequired
Ordered steps: { seq, action, activity_class, command?, repeat_count, targets?, creates? }. Retry cycles collapse into one step with repeat_count recording the repetitions.
draft.steps[].activity_classstringrequired
One of read, write, search, execute, other. A shell line is classified from the command itself, deterministically: pure reads and searches are downgraded out of execute, file mutations are upgraded to write, and anything ambiguous stays execute. Without this a harness that funnels everything through one shell tool records no reads at all, and with no reads there are no dependencies between procedures.
draft.steps[].targetsarray of stringoptional
The files a shell line actually touched, parsed from real write destinations (redirect, tee, cp/mv destination, mkdir, touch, sed -i) and read operands, never from every path-shaped token, and never from inside a heredoc body. Absent when the tool named its own file in command; present but empty means the line was parsed and touched none.
draft.steps[].createsbooleanoptional
The shell construct brought the file into existence rather than modifying something already there. Only a creation makes a later reader depend on this procedure.
draft.trigger_signatureobjectrequired
What makes this task recognizable later: the summary text, plus structural entities, file paths touched, commands run, tools used.
draft.preconditionsarray of stringrequired
The files that had to exist before the first write or execute, the context the procedure assumed. These are file paths, not the shell lines that read them: a grep is a step, the file it searched is the precondition.
draft.postconditionsarray of stringrequired
Verified outcomes, derived from a real success signal (exit code 0 or an explicit ok flag) on the final executing step. Empty when no outcome data was provided.
draft.embeddingarray of numberrequired
Similarity vector for the trigger text, or [] when skipped or the provider failed. Failure never blocks the draft.
draft.embedding_modelstringrequired
The model that produced the vector. Vectors from different models are never compared at recall time.
Request
curl https://memorable-extraction-api.memorable.workers.dev/v1/extract \
  -H "Authorization: Bearer $MEMORABLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "session_id": "run-183",
    "task_description": "rotate the TLS cert for api.example.com",
    "harness": "my-python-orchestrator",
    "tool_calls": [
      {"name": "shell",
       "input": {"command": "certbot renew"},
       "result": {"ok": true}},
      {"name": "shell",
       "input": {"command": "nginx -s reload"},
       "result": {"ok": true}}
    ]
  }'
Response · 200
{
  "draft": {
    "title": "rotate the TLS cert for api.example.com",
    "session_id": "run-183",
    "schema_version": "1.0.0",
    "trigger_signature": {
      "summary_text": "rotate the TLS cert for api.example.com",
      "entities": {
        "file_paths": [],
        "commands": ["certbot renew", "nginx -s reload"],
        "tool_names": ["shell"]
      },
      "search_text": "rotate the TLS cert ..."
    },
    "steps": [
      { "seq": 1, "action": "shell", "activity_class": "execute",
        "command": "certbot renew", "repeat_count": 1 },
      { "seq": 2, "action": "shell", "activity_class": "execute",
        "command": "nginx -s reload", "repeat_count": 1 }
    ],
    "preconditions": [],
    "postconditions": [
      "final command exited successfully: nginx -s reload"
    ],
    "embedding": [],
    "embedding_model": ""
  }
}

Embed a query

post/v1/embed

Query-side embedding for recall. Used only as a fallback, recall tries exact and lexical matching locally first (zero tokens, zero network), and the CLI prefers the embedding provider your GBrain already has configured.

textstringrequired
The task description to embed. Truncated to 2,000 characters.
Request
curl https://memorable-extraction-api.memorable.workers.dev/v1/embed \
  -H "Authorization: Bearer $MEMORABLE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "rotate the TLS cert"}'

Errors

Errors return JSON with an error code and, where useful, a detail message.

StatusCodeMeaning
401unauthorizedMissing or invalid bearer token.
400invalid_requestMissing session_id or tool_calls; detail says what's expected.
400invalid_jsonThe request body is not valid JSON.
413payload_too_largeBody over 8 MB.
429rate_limitedOver the 300-per-minute per-key limit; body carries retry_after_s.
429quota_exceededOver 5,000 requests for the day on this key; body carries retry_after_s.
403anonymous_keys_disabledPOST /v1/keys is closed; get a key through the device flow.
401unauthorizedAlso what an unknown path returns: auth runs before routing, so a wrong path never reaches a 404.

An embedding-provider failure is not an error: the draft still returns with embedding: [] and an embedding_error field, and recall degrades to lexical and exact matching.

Past the monthly allowance is a refusal rather than a degradation. The response is still 200 and still carries the draft, because nothing about the request was wrong and retrying it changes nothing, but it carries refused: "allowance_exhausted" and a detail sentence written for a person. No memorable is created: no row is written and no embedding call is made. Capture resumes when the period resets or the plan changes. The free allowance is 1,000 memorables a month with a one-time reserve of 500 behind it.

Rate limits

300 requests per 60 seconds per API key, enforced at the edge, and 5,000 requests per key per day. A body is capped at 8 MB and 2,000 tool calls, and the prompt at 2,000 characters. A typical integration makes one /v1/extract call per completed workflow, which sits far below both limits.

Security

  • What is kept, and only this. A workspace key writes one workflows row per prompt (the prompt text, the harness, the tool-call count, the judge's verdict) and one procedures row per admitted procedure (title, steps, trigger signature, conditions, embedding). The corpus is never sent. Request bodies are never logged, and only a SHA-256 hash of a key is stored.
  • Your database stays yours. The API never receives connection credentials. Writes happen on your machine through your existing GBrain connection, into an isolated source.
  • Pre-scanned input only.The CLI sends only content that has already passed GBrain's secret scanner, and refuses to send a session that was written unscanned.
  • Injection-hardened recall.Stored procedures are re-rendered as explicitly inert reference data, stripped of control characters and size-capped, before they ever reach an agent's context.