familiar is a hosted tool router with memory.
The current working setup path is the hosted API.
Use the hosted product directly:
https://familiar.monster
Create an account and get the first API token with curl:
curl -X POST https://familiar.monster/api/v1/accounts \
-H "Content-Type: application/json" \
-d '{}'You can then use the returned token for the rest of the API.
The CLI exists in this repo as a local development helper, but it is not the public install path right now because the package is not published yet.
familiar receives text from any channel, decides which tool to call, executes it via webhook, and remembers context across conversations — so the next message picks up where the last one left off.
- route natural language to the right tool via webhook
- remember context across messages and channels
- private threads that stay out of shared memory
- channel-aware continuity across web, messaging, email, and other inputs
- ask for missing details before calling a tool
- async executor callbacks for long-running work
Without familiar, every system that wants to expose tools through text has to rebuild the same things:
- parsing intent from natural language
- choosing the right tool
- asking for missing arguments
- keeping thread context
- remembering facts across conversations
- routing results back to the right channel
familiar owns routing and memory so your executors can focus on execution.
familiar is currently an MVP in progress.
The core shape is there, but the product is still being hardened and simplified before it should be treated as a stable hosted service.
The main endpoint is:
POST /api/v1/input
Every message goes through the same first step:
- A user sends input to familiar.
- familiar loads the relevant thread and memory.
- familiar decides what kind of response is needed.
There are then three main outcomes:
This happens when familiar can answer on its own.
Example:
- the user asks a question
- the answer is already in the thread or memory
- no outside work is needed
Why it matters:
- fastest path
- no tool call
- best for conversational continuity
This happens when the request is real, but important details are missing.
Example:
- “Update the spreadsheet”
- but familiar does not know which spreadsheet or which row
Why it matters:
- prevents bad guesses
- keeps work accurate
- lets familiar gather what the tool will need before calling it
This happens when work needs to be done outside familiar.
Example:
- updating a spreadsheet
- sending something to another system
- running a workflow or script
Why it matters:
- this is how familiar turns conversation into action
- familiar stays focused on the conversation
- the target stays focused on doing the work
flowchart LR
A["Web chat"] --> T["familiar"]
B["Email"] --> T
C["Messaging app"] --> T
D["Voice transcript"] --> T
T --> M["Threads and memory"]
T --> E1["Tool target A"]
T --> E2["Tool target B"]
T --> E3["Tool target C"]
This is the smallest useful setup path for connecting code to familiar and getting a working request through the system.
- Create an account and get a token.
- Define your tools in
familiar.json. - Sync the tools familiar can use for a user.
- Send user input to familiar.
- Let familiar call the correct tool target when work should happen.
There are live sandbox demos you can try without setting anything up:
/sandbox/demo-executor— basic todo tool/sandbox/async-countdown— async callback demo/sandbox/pinned-tool— shortcut tool demo (@tool-name)
What the sandboxes show:
- Demo executor — a full request from familiar to a tool target and back
- Async countdown — starts a 10 second timer, returns
acceptedimmediately, callbacks later viaPOST /api/v1/webhooks/executor - Pinned tool — invokes a tool with
@tool-name payload, applies only to the current message, can chain multiple invocations
If you are new to this, think of it like this:
- familiar is the thing the user talks to
familiar.jsonis the contract that tells familiar what tools exist and what arguments they need- your code or webhook is the thing that actually does the work once familiar has already extracted those arguments
- the example folder shows the smallest possible version of that target
Core terms:
account- the owner that pays for and manages familiar
token- the machine credential used to authenticate API requests to familiar
integration- the configured familiar connection for one app, bot, or deployment
executor- the script, service, or workflow runner familiar triggers to do real work
user_id- the end user identity within an integration
channel- the communication surface the user is speaking through, identified by
channel.typeandchannel.id
- the communication surface the user is speaking through, identified by
familiar.json is the sync manifest.
It is the source of truth for:
- which tools familiar may use
- what each tool does
- the exact schema familiar must satisfy before calling the executor
That means familiar should use the manifest to:
- choose the right tool
- extract arguments into the declared schema
- ask follow-up questions when required fields are missing
The executor should receive validated tool arguments, not raw user language that still needs interpretation.
Every API request needs this header:
Authorization: Bearer YOUR_INTEGRATION_TOKEN
That token identifies which connected system is calling familiar.
Use this endpoint to tell familiar which tools are available for a specific user.
In practice, this payload should usually come from your familiar.json file.
curl -X POST http://localhost:5173/api/v1/integrations/integration_a/users/user_123/tools/sync \
-H "Authorization: Bearer dev-token" \
-H "Content-Type: application/json" \
-d '{
"integration_id": "integration_a",
"user_id": "user_123",
"tools": [
{
"tool_name": "spreadsheet.update_row",
"description": "Update a spreadsheet row",
"input_schema": {
"type": "object",
"properties": {
"sheet": { "type": "string" },
"row_id": { "type": "string" },
"values": { "type": "object" }
},
"required": ["sheet", "row_id", "values"]
},
"status": "active"
}
]
}'Field guide:
integration_id- the current wire-format integration id
- this must match the token making the request
user_id- the end user who will be talking to familiar
- use a stable id from your own app
tools- the list of tools familiar is allowed to use for this user
tool_name- the name familiar will use internally when choosing a tool
description- a plain-language explanation of what the tool does
- familiar uses this to decide when the tool is relevant
input_schema- the expected shape of the tool arguments
- keep it simple and explicit
status- whether the tool is currently available
- use
activewhen the tool should be callable
Plain English example:
integration_id = "integration_a"means “this integration is called integration_a”user_id = "user_123"means “these tools are available for this user”tool_name = "spreadsheet.update_row"means “this tool updates a spreadsheet row”
What familiar should do with that schema:
- if the user asks for spreadsheet work, familiar should choose
spreadsheet.update_row - if the schema needs
sheet,row_id, andvalues, familiar should extract those fields - if one is missing, familiar should ask for it before calling the executor
Use this endpoint when a user sends a message into familiar.
curl -X POST http://localhost:5173/api/v1/input \
-H "Authorization: Bearer dev-token" \
-H "Content-Type: application/json" \
-d '{
"integration_id": "integration_a",
"user_id": "user_123",
"input": {
"kind": "text",
"text": "Update the sales sheet and mark Acme as contacted"
},
"channel": {
"type": "email",
"id": "chris@example.com"
}
}'Field guide:
integration_id- the current wire-format integration id
- tells familiar which tool set this conversation belongs to
user_id- the end user speaking through familiar
- this is how familiar keeps memory and threads tied to the right person
input- the actual thing the user sent
input.kind- the type of input
- for now, the main value is
text
input.text- the user’s message
- familiar expects normalized text only
- if your system starts from voice or audio, normalize or transcribe it before calling this API
- large transcription blocks are valid as long as they are still plain text
channel- where the message came from
channel.type- the kind of surface, such as
web,email, ormessaging
- the kind of surface, such as
channel.id- the identity of that surface for this user
- examples: an email address, a browser session id, or a messaging account id
channel.name- optional descriptive label for admin or UI use
- not required for routing or identity
Why channel matters:
- familiar shares memory at the user level
- but it can keep different recent thread continuity per channel
When familiar calls a tool target, the important part of the payload is the validated arguments object.
The target may also receive metadata such as:
integration_iduser_idthread_idtool_name
But the executor should not have to decide intent again or parse the user's natural-language request again.
Example:
If familiar.json says todos.add requires:
{
"todo_items": ["call dad", "buy dad a birthday present"]
}then familiar should send exactly that schema-shaped data to the executor once it is ready.
If the user forces a tool explicitly in conversation, that still arrives at familiar as ordinary text.
For example, @todos.add call dad and buy milk is not a separate input type.
Plain English example:
channel.type = "email"means “this came from email”channel.id = "chris@example.com"means “this specific email identity sent the message”
Use this endpoint to fetch the threads familiar knows about for a user.
curl http://localhost:5173/api/v1/integrations/integration_a/users/user_123/threads \
-H "Authorization: Bearer dev-token"This is mostly useful for admin tools, debug screens, or a UI that wants to show past threads.
- responses include
request_idandX-Request-Id - write routes support
Idempotency-Key - input is rate-limited per integration/user pair
- normal conversations are captured into memory by default
- private threads are excluded from shared-memory capture and retrieval
- OpenRouter is the default routing model for intent and tool choice
- familiar can optionally use Cloudflare Workers AI for routing if explicitly enabled
Optional routing model setting:
TEXTY_USE_WORKERS_AI_ROUTING- set this to
trueif you want familiar to use Workers AI for routing and extraction
- set this to
CLOUDFLARE_ROUTING_MODEL- use this to choose the Workers AI model for the first-pass routing and intent step
- if unset, familiar uses
@cf/meta/llama-3.1-8b-instruct-fast
CLOUDFLARE_EXTRACTION_MODEL- use this to choose the Workers AI model for schema-shaped argument extraction and follow-up argument updates
- if unset, familiar uses
@cf/qwen/qwen3-30b-a3b-fp8
CLOUDFLARE_DECISION_MODEL- legacy fallback that applies to both steps if the stage-specific Cloudflare variables are unset
OPENROUTER_ROUTING_MODEL- use this to choose the OpenRouter model for the first-pass routing and intent step
OPENROUTER_EXTRACTION_MODEL- use this to choose the OpenRouter model for schema-shaped argument extraction and follow-up argument updates
OPENROUTER_DECISION_MODEL- legacy fallback that applies to both steps if the stage-specific OpenRouter variables are unset
Recommended Workers AI split for familiar:
- routing:
@cf/meta/llama-3.1-8b-instruct-fast - extraction:
@cf/qwen/qwen3-30b-a3b-fp8
Why:
- the router should be fast and cheap
- extraction quality matters more than raw routing speed
- tool schemas and clarification updates benefit from the stronger extraction pass
Execution states:
completedneeds_clarificationacceptedin_progressfailed
Meaning:
completed- the tool finished the work
needs_clarification- more information is needed before work can continue
accepted- the tool accepted the work but has not finished yet
in_progress- the work is actively running
failed- the tool could not complete the work
When a tool returns accepted or in_progress, the intended follow-up path is a minimal executor callback to familiar at POST /api/v1/webhooks/executor:
{
"integration_id": "integration_a",
"user_id": "user_123",
"thread_id": "thread_abc",
"result": {
"execution_id": "exec_123",
"state": "completed",
"content": "Your import finished successfully."
}
}This is an async executor result callback, not familiar-owned task management.
familiar should append that result to the thread and handle notifying the user from there, rather than having the executor message the user channel directly.
If the executor retries that callback, familiar can dedupe via Idempotency-Key or, if no header is provided, via result.execution_id.
npm run devnpm run checknpm run buildnpm test