Memory graphs
The memory graphs service gives your account typed knowledge graphs: the people, things and ideas your agents learn about, as entities, and how they connect, as relationships. Use it for what an agent should still know next time and be able to navigate: who works where, what a decision rests on, which topics a person cares about.
A graph holds three things. Entities each have a type, a short label, an
optional summary, properties and raw data, and are named by their id.
graph_get_entity also takes its slug; every other tool takes the id. An
entity's slug is its type, a colon and its label in lower case, with every run
of characters other than a to z and 0 to 9 made one hyphen and none at either
end: a Person labeled Ada Lovelace is Person:ada-lovelace. A slug
already taken in the graph gets -2, -3 and so on, and a new label gives
the entity a new slug. Pass a slug exactly as an answer shows it.
Relationships each have a type and
join two different entities, from a source to a target, with optional
properties of their own. The vocabulary declares every entity type and
relationship type the graph may hold: each type's properties, which of them are
required, and for a relationship type the entity types allowed at each end.
Every entity and relationship written must be of a declared type and carry
every required property.
The vocabulary is open: your agents extend it themselves as they learn what a graph needs, and an addition applies at once, with no one to approve it. What holds it together is deduplication: a proposed type too like one already declared is turned away and the similar types named, so the existing type is used instead of a near-copy of it.
Your account owns every one of its graphs, and every agent of the account shares them.
It offers a trial, and it can also be enabled in full and billed per use.
Every tool works on this system's own storage only; none reaches outside it.
Its tools
Each tool is charged under an operation of its own, named in brackets at the
end of its entry: one unit per call, except the tools that take a list of
entities, relationships or ids to write, which charge one unit per list member,
up front, for every member sent, and graph_delete, which charges after the
delete for what it removed (see its entry). The tools that only read are reached through
toolsets_calltool_read, the ones that lose nothing through
toolsets_calltool_write, and the ones that replace or remove content through
toolsets_calltool.
Tools that only read
graph_listlists your account's graphs, oldest first, each with its id, name, description, the template it started from and when it was created and last changed (graph-list).graph_list_templateslists the templates a graph can start from, each with its id, title and description (graph-list-templates).graph_openreturns a graph's vocabulary, its entity and relationship counts, its own usage instructions and guidance on choosing a query tool. Open a graph before writing to it (graph-open).graph_find_entitiesfinds a graph's entities by words in their label or summary (searchTerm), by type (entityTypes), by exact property values (properties), or any combination, one page at a time (graph-find-entities).graph_get_entityreturns one entity, by its id or its slug: by default everything about it, including its properties, raw data and who created and last changed it (graph-get-entity).graph_get_relationshipslists an entity's relationships, each with its id, type, both ends and properties, one page at a time — for example to find a relationship's id before deleting it (graph-get-relationships).graph_exploreshows what surrounds an entity: the entities up todepthrelationships away, grouped by distance and by the relationship type that reached them (graph-explore).graph_find_pathsshows how two entities connect: the paths fromsourceEntityIdtotargetEntityId, each with its entities and the relationships it crosses (graph-find-paths).graph_traverseruns a multi-hop query: astart, a sequence ofstepswith type and property filters at each, and what to answer — the entities the last step reached, everything walked, the paths, or property values and counts (graph-traverse).graph_searchfinds entities whose label or summary matches the words inquery, across one, several (graphIds) or every graph your account holds, answered graph by graph, every graph searched present even with no hits; a graph found deleted while an all-graphs search runs is left out, and naming a deleted graph ingraphIdsanswersNOT_FOUND. Within each graph, hits come in order of label: nothing is ranked by relevance, within a graph or across graphs. It is charged once however many graphs it searches (graph-search).graph_timelineshows when an entity was created and last changed and which relationships were added to it, one page at a time (graph-timeline).graph_validatechecks a graph's existingentitiesorrelationshipsagainst its current vocabulary and answers one page of the issues found — for example after a vocabulary change. Each page says how many it checked, thenextOffsetto continue from and whether it isdone. It changes nothing; fix an issue with the write tools (graph-validate).
Tools that change things and lose nothing
graph_createmakes a new graph with anameand optionally adescription, its vocabulary and usage instructions copied from the template named bytemplateId. It is refused when your account already holds as many graphs as it may. A repeated call creates a second graph (graph-create).graph_create_entitiescreates entities in a graph, in order. Each needs a declaredentityTypeand every required property. A repeated call creates them again: entities are not matched by label, and one whose slug is taken gets a numbered slug. Charged per member ofentities(graph-create-entities).graph_create_relationshipscreates relationships between existing entities, by id, in order. Each needs a declaredrelationshipTypethat allows the types of the two entities it joins. A repeated call creates them again, since two relationships of one type may join the same entities. Charged per member ofrelationships(graph-create-relationships).graph_propose_vocabularyadds to a graph's vocabulary: new entity and relationship types, and new optional properties on types that already declare some. It is additive only: a proposal that would remove, narrow or rewrite anything — a type's description included — add a required property or delete a type is refused, naming it, and belongs tograph_revise_vocabulary. A property proposed for a type that declares none yet is answeredrejected, since it would be the type's first (see Read before you write). It is charged once however many proposals it holds (graph-propose-vocabulary).
Tools that replace or remove content, with no undo
Nothing in this service is versioned, so what these tools replace or remove cannot be recovered.
graph_updatechanges a graph'sname,descriptionorusageInstructions; give at least one. What it names is overwritten, the usage instructions replaced whole;nullclears the description or the instructions. The graph's contents are untouched (graph-update).graph_deletedeletes a graph and everything in it: its entities, relationships and vocabulary, and answers how many entities and relationships it removed. A delete cut short — on a timeout, say — may leave the graph partly removed and no longer openable; send it again to finish it. It is charged after the delete, for what it removed: one unit per entity (graph-delete-entities) and one per relationship (graph-delete-relationships), the same asgraph_delete_entitiesandgraph_delete_relationshipscharge; nothing for an empty graph or a graph already gone. Its size is not known until it has run, so before it deletes anything it is refused only when your account could not pay for one unit; open the graph withgraph_openfirst to see its entity and relationship counts. The charge cannot be refused once the graph is gone, so it is taken in full even past your wallet's balance: your credit may then go below zero, and every call to this service, and every priced call to any other, is refused until a top-up brings it back to zero (see Its limits). On a trial it takes only what the trial has left. A delete cut short charges nothing for what it removed; the repeat that finishes it charges what it removes.graph_update_entitieschanges existing entities, each named byentityId: itslabel,summary,propertiesordata.propertiesis a merge patch: a key it names overwrites the value the entity held, a key set tonullis deleted, and a key it leaves out is kept. What a change leaves out is kept. Charged per member ofupdates(graph-update-entities).graph_delete_entitiesdeletes entities, by id, and every relationship to or from each of them. Charged per member ofentityIds(graph-delete-entities).graph_delete_relationshipsdeletes relationships, by id; the entities they joined are untouched. Charged per member ofrelationshipIds(graph-delete-relationships).graph_revise_vocabularychanges a graph's vocabulary in any way: it replaces a type's description, removes or rewrites properties, adds required ones or a type's first property, replaces the entity types a relationship type allows or whether it is bidirectional, and deletes types. Deleting an entity type deletes every entity of that type and their relationships; deleting a relationship type deletes every relationship of that type; each result says how many. A type deletion sent again finishes it: it removes whatever of the type's data is left and is answeredapproved, or answeredrejectedas not found when nothing of the type is left. Rungraph_validateafterwards to find existing data the change left invalid. It is charged once however many proposals it holds (graph-revise-vocabulary).
Read before you write
Open a graph with graph_open before writing to it, and read its vocabulary
and its usage instructions. The vocabulary is enforced: an entity or
relationship of an undeclared type, a relationship between entity types its
type does not allow, or an entity missing a required property is refused.
When no declared type fits what you want to record, propose one with
graph_propose_vocabulary, then write. Each proposal is answered approved
or rejected, with the reason. A rejected answer that lists duplicates is
the deduplication catch, not a failure: the vocabulary already has a type like
the one proposed, and the answer names it, so use that type. A property the
type already declares is left as it is; to change it, use
graph_revise_vocabulary's updateProperties. Resending the whole call is
safe: it adds nothing new — a type already added is answered rejected as a
duplicate, a property already declared is answered approved and left as it
is, and any other change already applied leaves the vocabulary as it was —
though vocabularyVersion may advance.
A call that ended part-way carries that sentence as endedAt.resendInstruction,
and, when it held a type deletion, what sending the deletion again comes to as
endedAt.deletionResendInstruction. When vocabularyVersion is null, next
names the graph_open call that answers it.
A type that declares no properties takes any keys. Its first declared property
closes it: from then on, every key of its entities that the type does not
declare is refused, and the entities already written with other keys become
invalid. So graph_propose_vocabulary answers a type's first property
rejected; declare it with graph_revise_vocabulary if that is intended, then
run graph_validate.
The write tools that take a list write its members one at a time, in order,
and answer each: those written, and those refused with why, each named by its
0-based member index. Resend only the failed members, corrected where the
refusal asks for it; the others are written. A member refused with
MEMORY_GRAPH_ENTITY_CONFLICT lost a race for its slug and is simply sent
again as it is. An outage or a timeout ends the call at that member,
answered as endedAt, and no member after it is tried. A refusal never undoes
the members already written, so after an endedAt resend the failed members
and those from endedAt.member on. An endedAt member with code
MEMORY_GRAPH_QUERY_TIMEOUT was not written — the store rolled it back — so it
can be resent as it is; after any other code it may or may not have been
written, so look for it before resending it. A connection to the store lost
while the member was being written is one such other ending. The answer
says the same in fields: endedAt.resend is as_is or look_first, with
endedAt.resendInstruction beside it, and resendInstruction says which
members to resend whenever one was refused or the call ended early. A graph deleted
while a call writes to it keeps nothing the call wrote, and a call that meets
the graph gone part-way answers NOT_FOUND for the graph.
Choosing a query tool
Every graph_open answer carries this guidance with the vocabulary it is
about.
- To see what surrounds an entity you know, use
graph_explore; its answer is grouped by relationship type, which also shows what types exist around it. - To learn how two entities you know connect, use
graph_find_paths. - To look an entity up by label words, type or exact property values, use
graph_find_entities. - To look up by label or summary words across one, several or all of your
graphs, use
graph_search. It is text matching, in label order, with no ranking by relevance. - For multi-hop navigation with filters at each step, use
graph_traverse. - For everything about one entity — its properties, data and who wrote it —
use
graph_get_entity; for an entity's relationships with their ids, usegraph_get_relationships; for when it was created and changed, usegraph_timeline.
Discover before you traverse: use graph_find_entities or graph_explore
first. In a traversal, leave relationshipTypes out to follow every type, and
use one multi-step traversal rather than a chain of single-hop calls or a query
per relationship type. returnMode all shows the entities in between and
path the relationships walked. detailLevel brief cuts an answer's size:
brief is an entity's id, slug, type, label and summary, summary adds its
properties, and full adds its raw data and who wrote it and when.
truncated with truncationReason result_limit and hasMore marks a page
boundary: page on with offset up to the offset limit, and narrow the
traversal past it. Any other truncated answer was cut short: narrow the
traversal. The answer says which in fields: cutShortInstruction when it was
cut short, and offsetLimitInstruction, with nextOffset null, when more lies
past the offset limit. A graph_explore answer whose truncatedTypes names
any carries truncatedInstruction, saying how to see the rest.
Templates
A new graph starts from a template, which gives it a vocabulary and usage
instructions. graph_list_templates lists them:
empty, the default, declares no types: your agents propose them as they learn what the graph needs.conversationsholds what is learned about a person through conversation: their interests, preferences, goals, decisions and situational notes, with supersession, so a change is recorded as a new entity that supersedes the old one and keeps its history.personholds people and their relationships: identity, family and social connections, the organizations they work at, study at or belong to, where they live, and the events in their lives.legalholds contracts and what they contain: parties, clauses, obligations, rights, warranties, defined terms, governing jurisdictions and the contract templates agreements are derived from.
A template's usage instructions are copied into the graph when it is created
and become the graph's own: graph_open returns them, and graph_update
replaces them. A later change to a template changes no existing graph.
Paging and bounds
graph_find_entities, graph_get_relationships, graph_timeline and
graph_traverse answer one page at a time. When there is more, call again with
the next offset and every other argument unchanged: each answer gives it as
nextOffset, which is null when there is no page to ask for. graph_validate pages by
its own nextOffset until it answers done.
The most a call answers:
graph_find_entities: up to 50 entities to a page, 20 unlesslimitsays otherwise, filtering on at most 20properties.graph_get_relationships: up to 50 relationships to a page, 50 by default.graph_timeline: up to 50 events to a page, 20 by default.graph_search: up to 50 hits from each graph, 10 by default, from at most 1,000 graphs named ingraphIds. A graph with more matches than the limit answers its first by label, and says how many match in total; to reach the rest, narrow the words or entityTypes, or use graph_find_entities with searchTerm and offset on one graph.graph_explore: from 1 to 3 relationships away, 1 by default, and up to 50 entities for each relationship type at each distance, 10 by default.graph_find_paths: paths crossing from 1 to 5 relationships, 3 by default, and up to 50 paths, 5 by default.graph_traverse: up to 200 results, 50 by default, over 1 to 10 steps; a repeated step walks again at most 10 times, and a traversal reaches at most 10 hops in all: each step counts one, a repeated step counts its maxDepth. Each filter list holds at most 20 conditions, and a projection names at most 20 properties. Itsoffsetskips at most 1,000 results. Paging stops at the offset limit; to reach results beyond it, narrow the start, the types or the filters.graph_validate: up to 200 issues to a page, 200 by default.
The most a write call carries: 100 entities to graph_create_entities,
graph_update_entities or graph_delete_entities; 500 relationships to
graph_create_relationships or graph_delete_relationships; and 50 proposals
to graph_propose_vocabulary or graph_revise_vocabulary.
The longest text each field takes: a graph's name 200 characters, its
description 4,000 and its usage instructions 16,000; an entity's label 1 to
200 and its summary 4,000; a type name 100, a type's description 1,000 and a
proposal's justification 1,000; a property key 100; a search term 1 to 2,000;
and an entity reference, an id or a slug, 400. A type declares at most 50
properties, each named once in a list, and an enum property at most 100
values. A property's defaultValue is a value of its own type, and as text at
most 1,000 characters. A revision's removeProperties names at most 50
distinct properties.
A type name starts with a letter or an underscore and holds only letters, digits and underscores. A relationship type is written as the vocabulary answers it: upper-case words joined by single underscores, each word letters optionally followed by digits, or digits alone — WORKS_AT, LED_TO, HAS_2_FA.
How large a graph's vocabulary may grow is under Its limits.
Its limits
The most graphs your account may hold is the platform's limit, one value
for every account. It is a setting and can change, so it is not written here.
A graph_create that would take your account past it is refused, naming how
many graphs your account holds and the most it may, and changes nothing.
Deleting a graph frees its place. Lowering the limit deletes nothing: graphs
already over it stay readable, writable and deletable.
What your graphs hold accrues by the day. Once the service has been
enabled in full, the entities and the relationships your account's graphs hold
are counted once a day and that day's share accrues, each priced per thousand
held for a month; services_describe lists the two prices as the
memory-graph-time-based-entities (time-based) and
memory-graph-time-based-relationships (time-based) lines. Nothing is taken
from your credit then: what has accrued is charged on the billing date, the
first of each calendar month (UTC), in one entry for each price for the days
since the last billing date. Disabling the service does not stop the charge:
the graphs are still held, so they still accrue, until they are deleted. On a trial, storage is free but capped: a
graph_create_entities or graph_create_relationships that would take your
account past the trial's limit on entities, or on relationships, across all
its graphs is refused with STORAGE_TRIAL_CAP_EXCEEDED, naming the measure,
the limit (cap), what your account would have held (projected) and the
call that enables the service in full, and nothing of it is written. The two
limits are settings and can change, so they are not written here.
When your credit is below zero. The billing-date charge is taken in full
even when it takes your credit below zero. While your available credit is
below zero and the service is enabled in full, every call to it — reading,
updating and deleting included — is refused with
WALLET_INSUFFICIENT_BALANCE, naming the amount that brings your credit back
to zero (required) and the calls that buy credit; so is every priced call
to any other service. Your graphs are kept: nothing is deleted. Buying credit
that brings it to zero or above lifts the refusal on the next call.
The size of a graph's vocabulary is fixed: a graph's vocabulary holds at
most 200 entity types and 200 relationship types, and at most 262,144 bytes as
JSON. A proposal that would grow it past either is answered rejected and
changes nothing; reuse a declared type, shorten descriptions or enum lists, or
remove what is unused with graph_revise_vocabulary. A change that makes the
vocabulary smaller — deleting a type, removing a property, shortening a
description — is never refused for the size.
Changes sent at the same time. Vocabulary changes sent to one graph at
the same time are each applied against what the others wrote; one that keeps
colliding with them ends the call at that proposal, answered as endedAt
(MEMORY_GRAPH_VOCABULARY_CONFLICT) — open the graph and send again whatever
is still missing. The proposals approved before it were written and stay.
What it does not do yet
- Semantic search.
graph_searchandgraph_find_entitiesmatch words in labels and summaries, regardless of letter case; there is no search by meaning and no query syntax. - Import and export. A graph cannot be loaded from a file or written out to one.
- A history of prior values.
graph_timelinerecords that an entity was created and changed and which relationships were added, not what it held before a change. Nothing is versioned, so an update or a delete cannot be undone.