Browse the documentation
Save: agent_capture
Captures one memory. Required fields are content, thought_type, source_agent, source_phase and importance. The optional fields are scope, the attribution fields captured_by, actor_id and actor_type, and confidence, evidence, alternatives_rejected, decided_by, metadata, trigger_event and relations. Korium stores every one you send. korium_reconstruct returns confidence, evidence, alternatives_rejected, decided_by, metadata, trigger_event and relations under capture, with each relation and whether it became a link. Search reads content, so put anything the next reader needs there as well.
Each field has a set shape: trigger_event is text, metadata and decided_by are objects, evidence and alternatives_rejected are lists, and confidence is a number from 0 to 1. Korium sets no size limit of its own on these fields.
Each entry in relations has a target_id, a relation_type and an optional context. The types are supersedes, triggered_by, evolves_from, contradicts, supports and synthesized_from. The same target and type twice in one save is refused. The link runs from the new memory to the target and is written when the memory is filed, usually a few seconds after the save. The context is kept in the memory's capture record, not on the link, so korium_trace does not show it.
A supersedes entry also retires its target when the new memory is filed. The target stops being current, search no longer returns it, and it records the new memory as what replaced it. This happens on every plan, and no model is involved. Only a decision, a preference or a rejection can replace one of those three: when the target is one of them and the new memory is not, the link is written and the target stays current.
The save's reply lists each supersedes target under replaces, with what is expected to happen to it: will_retire, stays_current, already_retired, or decided_when_filed for a target saved moments ago or one Korium could not check in time. The reply is what is expected, because the memory is not filed yet. capture.relations on the new memory, read through korium_reconstruct, shows what happened: retired is true or false, and not_retired gives the reason. The reason retire_failed means the link was saved but the target could not be retired at that moment; send the link again with korium_relation. To undo a replacement, see Correct a link.
A target_id must be the ID of a memory in your own account, written in the 36-character form. A memory you saved moments ago counts. If a target is not found, the whole save is refused with error -32602, nothing is saved, and the message names that ID. A memory in another account is answered the same way as one that does not exist. If Korium cannot check its database in time, it lets the save through rather than lose the memory; a link it then cannot write is recorded as not written in capture.relations.
A field with the wrong shape, or containing a NUL character (\u0000), is refused with error -32602, and nothing is saved.
A successful acknowledgment includes thought_id. Check it and any refusal or warning before reporting success. The acknowledgment precedes later extraction and conflict analysis, so it does not certify that the new statement agrees with everything already stored.
See a complete capture example. To link the new memory to another one, put the link in relations, or call korium_relation after the capture. Either way, a capture receipt does not prove the link exists: use korium_trace, or read capture.relations through korium_reconstruct, once the memory is filed.
Find: agent_search
{
"query": "Why did we change the release check?",
"scope": "onboarding.demo",
"limit": 10
}Search combines keyword and vector retrieval, with ranking by match, importance and freshness, and related context. It returns source thoughts as well as retrieval information. Default result count is 10, with a maximum of 50.
Use focused questions, and inspect the underlying memories before treating a summary as an approved decision. A search either answers or reports an error; it does not hand back part of a page. See how ranking works.
Korium answers two kinds of question directly: what someone said about a topic, and how two people or things are connected. A question that asks how many, such as "how many meetings has Dana attended", is an ordinary search and gets the usual results page; Korium no longer answers it with a count.
Names in a question
A name in a question is matched to a stored name whatever its capitals and accents: "Milene", "MILENE" and "Milène" are one name, and so are "İpek" and "Ipek". One case is different: a person's name with a lower-case word in it, such as "Guido van Rossum". A "what did X say" question about it is answered when you type the name exactly as it is stored, but typed with a capital missing, as "guido van Rossum", it gets the results page. Punctuation counts as a space, so "Jean-Luc" is "Jean Luc". A "#" anywhere in a word is part of the name, so "C" and "C#" are two names and "#general" is not "General". A "+" at the end of a word is part of what a question asks about, so a question about "C++" does not find "C", and "C++ code" is "C++" and "code". Where a question names people, a "+" always joins two names, however it is spaced, as in "Bob+Dana", "Bob+ Dana" or "Dana and Bob+", and a connection question about a name ending in "+" gets the results page. Names stored before this rule keep what they were joined to: a memory about C that was attached to an earlier "C++" stays with it. A dash standing alone is the exception: a name typed across one, such as "Jean - Luc" or "Jean" and "Luc" with a long dash between them, could be one name or two, so an exact-words question gets the results page instead. Type "Jean-Luc" with no spaces. A question that names two stored people, with "and", "with", "or", a dash standing alone, a comma, "&", "/", "+" or ";" between them, such as "what did Bob & Dana say about the budget", also gets the results page, because it cannot be answered about one of them. A stored name typed across a comma, "&", "/", "+" or ";", such as "John Smith, Jr.", could also be one name or two, so it gets the results page too; type "John Smith Jr". A letter that is not a plain letter with a mark on it is not changed: "Søren" is not "Soren", "Łukasz" is not "Lukasz", "Straße" is not "Strasse", and the Turkish dotless "ı" in "Işık" is not "i". A different letter is still a different name: "Dana" is not "Diana".
Only names are matched this way. The words inside a memory are searched as before. A stored name longer than 512 characters is not matched as a name: it is never the person or thing an exact-words or connection question names, and a connection question does not find it by its words. Only the topic of an exact-words question ("what did Dana say about the ledger") can still match it by its words.
When two stored people or things hold the same name once capitals and accents are ignored, such as "Zoë" and "Zoe" stored apart, a question about that name does not pick one. The answer says ambiguous_name and the ordinary results follow. A search with thought_type or stance skips this step, so it never says ambiguous_name.
Search only some memory types
{
"query": "flaky checkout test",
"thought_type": ["lesson", "correction"]
}Add thought_type to search only memories of those types: one type, such as "lesson", or a list. Lessons and corrections are where past mistakes are written down, so the example asks what went wrong before with that test. The page holds only those types, ranked by the same rules as a search without the filter, and a memory worded differently from your question is still found by meaning. Related memories are followed only to memories of those types. Neither the exact-words nor the connection answer is tried, because each would cover every type, so answer.resolution is not_attempted.
Search has no relevance cutoff, with or without this filter: it fills the page with the nearest memories by meaning. With the filter, those are the nearest memories of the types you named, however many you hold. So when fewer of them are about your question than the page has room for, the rest of the page is filled with ones about something else, and nothing on the result marks them. Read each one before you rely on it.
The types are the ones agent_capture takes. A name that is not one of them is refused with error -32602 before anything is searched, and the message says which entry in your list it was. So is an empty list, and so is thought_type sent with stance, which picks its own types.
scope narrows the same search by its label: "billing" matches only that exact scope, and "billing.*" matches it and every scope beneath it. See checking the scope.
Search one place
{
"query": "launch pricing",
"place": "Marketing"
}Add place to search one place you can already read: "notebook" for your own notebook, "shared_with_me" for memories someone shared with you alone, "everyone" for Everyone, or a workspace by its id or its name, with capitals ignored. A workspace with no name yet is reached by its id. Those three words always mean those places, even when a workspace has the same name; a workspace with such a name is still reached by its id. When two workspaces you can read share a name, both are searched. A memory in Marketing and in Everyone is found by either place.
A place only narrows what you can already read, so a place you cannot read, or one that does not exist, finds nothing, and the answer looks the same either way. An empty or non-text place is refused with error -32602. Each memory on the page still says where it lives, under places.
Ask for your current position
{
"query": "ABC or XYZ",
"stance": "position"
}Add "stance": "position" to ask what you have adopted, and not everything you have come across. A position is a decision, a preference or a rejection. Every other memory is a finding, a lesson included. With stance, the page holds positions only. A position is on the page when it holds at least one of the words in your question; among those, closeness in meaning sets the order. Related memories are followed only from one position to another, among each memory's 25 newest links. Leave stance out and search returns what it always has.
Each position carries beneath, with two lists of findings. support lists the findings joined to it by a supports link, up to 10, newest link first, and total is the full count. Korium no longer writes these links itself, so new ones are links you or your agents saved; links Korium wrote before this change are still listed. related lists up to five current findings nearest to the position by meaning, nearest first, each with its similarity, and only those at a similarity of 0.7 or more, unless your account was set up with another value. Related means only that: it does not say the finding agrees or disagrees. A long finding is stored in pieces, and each finding is listed once: by its nearest piece, with container_id naming the whole finding (it is empty for a finding stored whole). The list can hold fewer than five while more related findings exist, when a few long findings have many pieces close to the position. Only the first 10 positions on the page get a related list. When the list could not be worked out, because the position is past the first 10, it has no stored meaning to compare, or the read did not finish in time, unavailable is true and the list is empty; the rest of the answer is unaffected. A finding is shown as its first 300 characters, with truncated true when there is more; read the rest by its id. These findings are not ranked results and never take a place on the page. Both lists are worked out the same way on every plan, so an empty list means there is nothing there, unless unavailable is true.
When no position matches, the page is empty and stance says so: result is no_position_matched, message is "No saved position matched those words.", and findings.count is the exact number of findings that hold at least one of your question's words. It says nothing matched, not that nothing was decided.
With it comes might_be_related: up to three positions nearest your question by meaning, each shown as its first 300 characters. A long position is stored in pieces, and for one of those the entry is the piece nearest your question: id is that piece, the text is that piece's first 300 characters, and container_id is the whole position it belongs to (it is empty for a position stored whole). Each position is listed once. The list can hold fewer than three while more exist, when the nearest pieces belong to fewer than three positions. They are listed under that label and are not on the page, because they are not the answer: Korium cannot tell that "we chose Goobar" is about a database, only that it shares no word with "which database". When your account holds no position at all, the list is empty. It is also empty when the list could not be read in time: the list is a hint, so a slow read costs you the hint and never the answer.
So a position worded differently from your question is not on the page, and neither is any position when your question holds only common words such as "what should we do about this". Look under might_be_related, use the words the position would use, or search without stance, which also matches by meaning.
A position that shares a word with your question may still be about something else. Read it before you rely on it. Any other value of stance is refused with error -32602.
Each account may run 60 memory searches a minute, shared by everyone and every agent in it. Past that, the search is refused with error -32029, retryable: true and retry_after_ms, the wait in milliseconds before a search will be accepted again. Wait that long and search again; the refused search cost nothing. Every search that starts counts, including one that then failed and was tried again.
Read the response, including its limits
This shortened, synthetic response illustrates the first-memory exercise. The returned id should match the thought_id from your own capture receipt. Fields omitted here still matter when you inspect a real response.
{
"schema_version": "6.2",
"query": "What must happen before release?",
"result_count": 1,
"thoughts": [
{
"id": "11111111-1111-4111-8111-111111111111",
"scope": "onboarding.demo",
"type": "decision",
"content": "Demonstration only: the data owner checks totals before release because the previous example had mismatched totals.",
"fused_score": 0.0328,
"final_score": 1.48,
"fade_part": 0,
"places": [{ "kind": "notebook", "yours": true }]
}
],
"entities_partial": false,
"related_entities": []
}| Field | How to read it |
|---|---|
schema_version | The shape of this response. It changes when fields are added or removed. It is "6.2": "3.0" when the current-position answer dropped its comparison fields, "3.1" when unclear_people was added to a thought, "4.0" when a person's one-line description was removed, "5.0" when the count answer was removed, "6.0" when a related person's first-stored date, valid_from, was removed, "6.1" when places was added to a thought, and "6.2" when spoke_in was added to each related person or thing. Every caller gets the newest shape; there is no way to ask for an older one. |
thoughts[].places | Where the memory lives, as the dashboard's Memories screen shows it. Each item has a kind: everyone (its name is "Everyone"); workspace, with the workspace's id and its name when it has one; person, someone it is shared with alone, with their account id, their name when Korium knows it, and you, true when that person is you. A memory in none of those is in its owner's notebook alone, and its one item is notebook with yours, true when the notebook is yours. A memory in more than one of those lists every one; its owner's notebook is named only when it is in none of them. A memory quoted in answer, a finding listed under a position, and a position that might be related carry it the same way. Pass place to search one of them. |
thoughts[].id | The source memory. Keep it when checking a claim or making a correction. |
thoughts[].final_score | The number the results are ordered by, highest first: the match, plus the memory's importance times its weight (0.6 by default), plus fade_part (the score, in plain words). A memory that came onto the page from below it is not scored by its own match. Before its fade_part is added, it gets the lower of two numbers: the lowest score before fading among the memories the first pages hold, and its own importance part with the weakest match those pages hold. Tuning Memories Fade has the rule. |
thoughts[].fade_part | What the memory lost of its final_score for its age: 0 for a memory saved today or of a type that never fades, and a negative number, never further below 0 than the Memories Fade strength, for an older one. It is 0 on every result when Memories Fade is off. See tuning Memories Fade. |
thoughts[].unclear_people | Present only on a memory that names a person Korium could not tell apart from another of the same name. One item per such name: the name, the person_ids of everyone it could be, and people, the same people each as its person_id. The memory is linked to none of them until you settle it. A memory quoted in answer carries it the same way. |
result_count | The number of thoughts in this response, not a complete count of everything your question could refer to. |
entities_partial | True means related-entity context, or the names Korium could not settle (unclear_people), could not be fetched. The returned memory results may still be useful. |
related_entities[].spoke_in | The meetings on this page the person spoke a turn in, each named once by its id: the container_id of a result from that meeting, or the result's own id when its container_id is empty. A result from a meeting you cannot read has an empty container_id, so it names no such meeting. It comes from the speaker record Korium keeps for each turn of a transcript saved since speaker records began, read only from meetings and turns you can read. A meeting missing from the list may simply have no speaker record, so an empty list means Korium has no record of them speaking in these meetings, not that they did not. A link written by hand, or one the reading model guessed before speaker records, does not count. A person who spoke in a meeting on the page is listed even when no result links to them, with related_to naming that meeting's results; there is no limit on how many. The field is left out of every entry only when the speakers could not be read, and then entities_partial is true. |
There is no partial-result flag on a memory search, and there used to be. partial, degraded_leg and what_this_means are gone from the response, and provisional, vec_rank and kw_rank are gone from each thought. A memory search is now a single database query: it answers with a whole page, or it returns an error saying it could not answer. Nothing comes back half-done, so there is no flag to read. Each thought’s fused_score, its combined keyword and meaning score, is still there. It is the match alone and was never the number that set the order; final_score is.
If you have code reading any of those six fields, it is reading a field that is absent, which in most languages is indistinguishable from a search that reported no problem. Delete the check rather than letting it quietly pass.
An empty result is still not proof of absence. Check the scope and the wording, try the search again, and say what is uncertain; do not turn an empty list into a definitive “none.”
related_entities: [] with entities_partial: true means the related-context fetch failed. With false, it still does not prove the world contains no relationship: the graph may not contain every relevant source. Use the memory and connection checks to inspect what was captured.
A person in related_entities carries their id, name, entity_type and core_type, related_to (the memories on the page that name them), and invalid_at. It does not say when the person was first stored. There is no one-line description of a person. If a memory is shown on the wrong person, correct the link.
Which stored person a name means is decided by a fixed rule, on every plan, with no model. When one stored person has the name, the memory goes to them. When several do, Korium compares what each has in common with the memory: the other people and names it mentions, its scope or a scope above or below it, and who saved it. The person who clearly leads gets the memory. When nobody clearly leads, the memory is linked to none of them and its entry in thoughts carries unclear_people.
A tool returning is not always a successful save
A capture acknowledgment can contain queued: true, a thought_id, created_at, captured_by, conflict_flag and related_ids. The acknowledgment’s conflict flag is not a completed conflict check; background analysis happens later.
A plan or payload refusal may arrive in a normal tool response rather than as a protocol error. This shortened example is a refusal, not a memory receipt:
{
"status": "rejected",
"reason": "usage_restricted",
"class": "quota"
}quota means an allowance needs attention, payment means the payer needs to act, and payload means this capture needs changing. Repeating the identical call does not fix those conditions. Follow the message, preserve the unsaved work safely and do not mark the capture complete.
Validation, authorization and infrastructure failures can instead arrive as tool or protocol errors. Check both forms. If transport failed after a possible acceptance, search for the distinctive example or inspect its receipt before retrying a write, so an uncertain result does not become duplicate memories.
Inspect: korium_browse and korium_trace
korium_browse lists memories with pagination and filters for scope, type and status. Use it to inspect a project’s records or locate a specific kind of memory when semantic search is not the question.
korium_trace follows relationships from a memory ID. Choose forward, backward or both directions and a depth appropriate to the question. Maximum depth is 20; increasing it adds context, not certainty.
{
"thought_id": "<memory-id>",
"direction": "both",
"max_depth": 3
}For browse, status: "active" selects current records and status: "invalidated" selects records no longer current. Omitting the status filter lets you inspect both in the current interface. Check the original IDs and replacement information; this is different from expecting ordinary search to return every old record.
Use korium_stats for the memory statistics your connection is permitted to see. Statistics show activity and shape, not whether every statement is correct.
Connect: korium_relation
Links two existing memories with a typed relationship. For a correction, the source is the new memory and the target is the one it replaces.
{
"source_id": "<new-memory-id>",
"target_id": "<old-memory-id>",
"relation_type": "supersedes"
}A supersedes link retires its target as it is saved, on every plan, with no model involved. The target stops being current, search no longer returns it, and it records the source as what replaced it. A long memory's stored pieces are retired with it. The reply's retired is the ID of the memory that was retired, and message says so in a sentence.
When nothing is retired, the link is still saved, retired is null and not_retired says why:
only_a_position_replaces_a_position: the target is a decision, a preference or a rejection and the source is not one.already_retired: the target was not current, so nothing changed.source_not_current: the source is itself retired, and a retired memory replaces nothing.link_marked_wrong: this link was marked wrong withkorium_relation_correct, and a link marked wrong retires nothing, however often it is sent again. To replace the target, save a new memory with its ownsupersedeslink.
A link aimed at one stored part of a long memory retires that part only, and message says the rest of the memory stays current. To replace the whole memory, use the ID of the whole memory, which search returns as container_id.
A supersedes link saved before this behavior was released retired nothing, and still does not. Sending the same link again retires the target. To undo a replacement, see Correct a link.
Other supported relationships include triggered_by, evolves_from, contradicts, supports, derives_from, part_of and alternative_to. Read the live enum rather than assuming every domain relation is accepted by every tool.
alternative_to means "also true, not adopted". Korium wrote it itself until October 2026, when a model set a finding against a position; it no longer does, and the current-position search no longer shows it. Links written before then stay stored.
Verify both IDs and the direction before writing a link. Use a trace afterward when the relationship will guide future decisions.
Correct a link: korium_relation_correct
Says a link you were shown is wrong, without deleting it. from_id and to_id are the two ends of the link as they were returned to you, and verdict says what is wrong:
wrong_edge: the two things are not related at all. Send the link'srelation_type.wrong_type: they are related, but not like that. Send the link'srelation_type, andcorrected_typewith your own one-word name for what it should be.wrong_person: a memory is linked to the wrong person, for example the wrong one of two people who share a name.from_idis the memory andto_idis the person it is linked to now. For a name the memory shows inunclear_people,to_idis any one of theperson_idslisted there. Name the right person withright_person_id, or sendnew_person: truewhen the right person is not stored yet. When the name is not a person at all in that memory, for example a project called Dana, sendright_thing_idwith the ID of the stored thing it really is, ornot_a_person: truewhen there is no stored thing to name. Send exactly one of the four.relation_typeis not needed. Only the memory's owner may sendwrong_person. A memory with no owner, such as an old Team memory saved before memories had owners, is corrected by an account admin. Anyone else is refused with error-32602and nothing changes.
{
"from_id": "<memory-id>",
"to_id": "<person-id-it-is-on-now>",
"verdict": "wrong_person",
"new_person": true
}The first two verdicts record what you said and leave the link where it is. Either verdict on a supersedes link does one thing more: the memory that link retired becomes current again, search returns it again, and the reply's restored is its ID. That is the way back from a replacement made by mistake. It also applies to a supersedes link Korium wrote itself after comparing two memories. A memory that was retired for another reason, or replaced by a different memory, stays retired, and restored is null. So does a long memory that was replaced by an edited copy of itself. One stored part of a long memory does not come back while the long memory itself is retired: the reply says so, and sending the same correction again once the long memory is current brings the part back. When the long memory can never be current again, because it was retired for another reason or no correction can bring it back, the reply says that instead and the part stays retired.
wrong_person moves the memory to the right person and answers with status: "moved", the right person's person_id, created_person and recheck_queued. When the two people have the same name apart from capitals and accents, Korium decides again, with the same rule, the other memories linked to the person the memory was on, 12 at a time, leaving alone any memory whose person somebody already corrected, and recheck_queued is true. A memory where nobody clearly leads comes off the person and shows the name in unclear_people until somebody settles it. It stays where it is instead when somebody took it off one of those people, or it is still on one of them under another spelling. The check starts about a minute after your correction and takes up to 12 memories about every 75 seconds, so a person linked to 50 memories is checked within about seven minutes and a person linked to 20,000 takes about 35 hours. That pace is for your whole account: several people checked at once share it, the share each one gets is not fixed, and one person's check can run slowly for a while. Every hour Korium also picks up memories that are still waiting. recheck_queued is false when the names differ, and when the correction settles a name shown in unclear_people on a stored person, since that changes nobody's name. This works the same on every plan. Korium records who made the correction from your sign-in, and does not move that memory to another person again on its own.
With right_thing_id or not_a_person, the memory comes off that person under every name it used for them. With right_thing_id the memory is then linked to that thing as something it mentions, and the answer is status: "moved" with the thing_id. The part the person played, such as attending a meeting, is not carried over to the thing. With not_a_person the memory is linked to nothing new and the answer is status: "removed". Either way Korium does not link that memory to that person again on its own, and no other memory is checked again. right_thing_id must be something stored in your account that is not a person; a person is refused with a message telling you to send right_person_id.
If the thing you named was the wrong one, send the same call again, with the same from_id and to_id, and the right right_thing_id. The memory's link to the wrong thing is removed and it is linked to the right one instead, so searches and answers show the right one, and that holds when the memory is read again. When the memory itself also names the wrong thing, that link of its own stays. The answer is status: "moved" with the new thing_id. Both corrections are kept, each with who made it. The same works after not_a_person, to name the thing after all. Sending the thing the memory is already linked to is refused. Putting the memory back on the person, or taking the thing off and naming none, cannot be done this way: the memory is no longer on the person, so those calls are refused.
Two different people may share exactly the same name, spelled the same way. new_person: true makes a second person with the name of the person the memory was on, created_person is true, and the other memories on the first person are checked again as above.
A wrong_person correction changes the one memory you name. For a long memory saved in parts, that is the one part.
A memory or person that is not in your account is refused with error -32602, in the same words as an ID that does not exist. Sending the same correction twice is refused the second time, because the memory is no longer on the person you named, or, for a thing, because it is already linked to that thing.
Import: korium_hydrate
Imports an array of prepared memories. Each entry includes the content, memory type, source agent, phase and importance, with optional scope and capture attribution. Review the batch before sending it.
Imports use total room rather than the ordinary monthly save allowance. A batch that does not fit is refused as a whole. Follow the tool’s current batch limits and your plan’s import entitlement instead of splitting a refusal into blind retries.
A call takes at most 500 memories, so an import larger than that is several calls. Send the size of the whole import as total on every call: on a free account, the first call's total is the size of its one import, and later calls within 24 hours are accepted until that size is reached. An import in which nothing was saved does not use up that one import. Give each batch its own batch_id. If a call's answer is lost, send the same batch again with the same batch_id: it returns the first result and stores nothing twice.
Names and domain vocabulary
korium_list_provenance lists recorded agent names or work phases. korium_set_provenance_alias lets a tenant administrator adjust the displayed name without rewriting the stored record. Use this for a typo or rename, not for changing who actually made a decision.
korium_register_vocabulary adds a domain entity or relation type based on a core type. Register the entity types before using them to constrain the ends of a relation. Choose words your team already uses and test extraction with representative examples.
Vocabulary hints have size and count limits in the live schema. More synonyms are not automatically better. An overly broad label can connect unrelated things with greater confidence, which is the opposite of what you want.
Locate code: code_locate
{
"repo": "rootsha:<repository-root-sha>",
"head_sha": "<git-rev-parse-HEAD>",
"branch": "feature/refunds",
"query": "RefundPolicy calculateRefund",
"limit": 5
}Returns symbol locations and the index commit, along with freshness information. Pass the actual checkout SHA and the repository identity from the CLI. Verify stale candidates locally before relying on their line numbers.
Each branch of a repository has its own index. branch is optional: send it to search that branch’s index, or leave it out and the repository’s trunk answers. HEAD is refused, because it is what a detached checkout prints rather than a branch. The answer’s branch and is_trunk say which index answered, and freshness is judged against that branch’s own indexed commit. If the repository has no index for the branch you named, nothing is searched: staleness.reason is branch_unknown and known_branches lists the branches it does hold. A repository indexed only on branches, with no trunk, answers a search that names no branch with repo_unknown and known_branches.
When the answer means this checkout’s index is missing or behind (anchor_candidates, sha_miss, branch_unknown or repo_unknown), it carries next_step, an object with four strings: command is always korium-cli index, why says what is missing, how says where to run it, and install says where to get korium-cli. staleness_warning ends with the same how and install sentences. A current answer has no next_step.
Korium also sends these instructions to a client when it connects: “Search code with code_locate. When a code_locate answer carries next_step, next_step.why says what is missing and next_step.how says where to run next_step.command. If the answer lists known_repositories and one of them is the repository you are in, send that as repo instead of indexing. After korium-cli index finishes, search again with branch set to the branch the checkout is on. korium-cli runs on the person's Mac, and when that Mac is not signed in it opens a sign-in in their browser and waits for them. To keep the index current after each ordinary commit, suggest the person run korium-cli hooks install; do not run it for them, because it adds a git hook to their repository, and to every repository on their Mac when git shares one hooks folder between them.”
korium-cli search is the terminal path to code-location search, not a substitute for memory retrieval.
List code indexes: code_repositories
Lists the account’s code indexes, one entry per branch index: a repository indexed on its trunk and two branches is three entries. It takes no query.
{
"limit": 50
}Each entry carries repo_display (the name korium-cli registers), repo_fingerprint (the repository identity, such as rootsha:…), repo_id, branch, is_trunk, indexed_by_account_id, indexed_at and indexed_git_sha (when and at which commit it was last indexed), reading_version, last_used_at (the last search it answered, to the hour), last_sealed_at (when the idle clock started: the last completed index, or the creation of an index that never completed, or the day a former trunk stopped being the trunk, whichever is latest), days_until_reap, card_count and file_count. reading_version is the version of the client that read every symbol the index holds, and null when that is unknown or more than one did. days_until_reap is how many whole days are left before an idle branch index is removed, 0 meaning the next sweep; it is null for a trunk that has completed an index, which is never removed for being idle. A trunk whose index never completed counts down like a branch.
Entries are ordered by repo_fingerprint, then branch. limit is 1 to 100, default 50. When there are more, send the answer’s next_cursor back as cursor for the next page; it is null on the last page. An account with no index answers repositories: [] and count: 0. From the terminal, korium-cli repos shows the same list.
Delete a branch index: code_delete
Deletes one branch’s code index, the trunk included. Your git branch and source are not touched.
{
"protocol_version": "1",
"repo_fingerprint": { "root_sha": "<repository-root-sha>" },
"branch": "feature/refunds"
}It deletes that branch’s index and nothing else, and when it refuses it deletes nothing. A refusal is status: "rejected" with a reason and a detail sentence:
| Reason | What it means |
|---|---|
repo_unknown | This account has no index for the repository. |
branch_unknown | No index for that branch; branches lists the ones that exist. |
index_update_in_progress | An index of that branch is being built right now. Try again after it finishes. |
promote_required | In this account only someone who can publish the repository’s index may delete any of it. |
delete_refused:last_branch | It is the repository’s only index. |
delete_refused:trunk | It is the trunk, the index a search that names no branch reads. |
The last two are answered by sending the same delete again with intent: "truncate", which says you mean it; korium-cli delete BRANCH --allow-removals does that. The others are not. On success status is deleted and remaining_branches lists what the repository still holds. The dashboard’s Delete removes an index the same way. Besides those, the idle sweep removes a branch 14 days after the later of its last completed index and, for a former trunk, the day it stopped being the trunk. It never removes a trunk that has completed an index. When Code Indexer you bought ends, Korium also deletes the code indexes of every workspace under your account within 15 minutes, except a workspace that has Code Indexer from somewhere other than your purchase.
Build an index: code_submit
korium-cli index handles the submission protocol. Use it rather than constructing code-index uploads yourself. Two fields matter if you read the protocol:
trunk, sent when the upload starts, names the branch your remote calls its default. The branch equal to it becomes the trunk once that index is completed by an account allowed to publish it. Withouttrunk, the first branch a repository ever indexes becomes its trunk, and later uploads that don’t name it never move it. korium-cli sendstrunkonly when git records the remote’s default branch.reading_version, also sent when the upload starts, is the version of the client that read the files.code_repositorieslists it for the branch once a completed index shows that version read every symbol the branch serves.
retrack is refused: each branch is its own index, so there is nothing to move.
Fetch instructions: korium_get_toolkit
Use op: "guide" for the introduction, op: "manifest" for the file list and README, and op: "file" with a returned rel_path for one file. The manifest includes hashes that can be checked before writing fetched files locally.
The starter toolkit contains text instructions, skills, a sample agent and hook guidance. It does not install the CLI, authenticate another client or run a workflow. Review its contents against the current client and preserve existing project files when adopting it.
If your account exposes workflow configuration tools, use them for the configuration they advertise. A config read is not a skill download or a workflow execution.
Use the setup checklist before changing an automated workflow. Check your installed client’s help if its interface differs.