# Korium tool reference

Your assistant normally calls these tools for you. If you are writing an integration, use the live tool schema your connection receives. Names may have a client-specific prefix; the suffixes below are stable identifiers. No tool shares a memory, stops sharing one, moves one into a workspace, promotes one to Everyone or answers a request. Those steps are done by a person on the dashboard. A tool can find your waiting requests and explain them, and it can change or delete only your own memories.

## 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. An AI tool can replace only your own memories. On Small Business and Enterprise, where memories are shared by hand, that means a memory you have not shared. On Team, where every memory is shared with the team, your own memories can still be replaced. 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 you can read 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, or one you cannot read, is answered the same way as one that does not exist.

A `supersedes` link is also refused, with error `-32602` and nothing saved, when the target is a memory the AI tool may not change. The `reason` in the error is `relation_target_not_its_owner` when the memory is someone else's, `relation_target_shared` when it is yours but shared (on Small Business and Enterprise), and `relation_target_waiting` when a request is waiting on it. Do these on the dashboard instead. 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](what-you-can-store.html#required). 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](how-memory-works.html#relevance).

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](working-with-memory.html#scope-check).

### 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](memories-fade.html#formula)). 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](memories-fade.html#deeper) 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](memories-fade.html). |
| `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](release-status.html#memory) 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: how many memories the account holds and how much room is left, broken down by type, by the agent that saved each one and by the kind of work. `scope` narrows the counts to one branch, and `projected` asks whether there is room for a run of that size. Statistics show activity and shape, not whether every statement is correct.

## Requests: korium\_waiting\_requests

Lists the requests waiting for you: the ones waiting for your answer, and the ones you asked that still wait. It takes no arguments. Each request says what kind it is, who asked, which workspace or memory it is about and why it waits. A memory is named by its ID and never by its words; `korium_reconstruct` reads one you are allowed to read.

The tool only reads, and no AI tool can say yes or no to a request. The person answers it in their Inbox on the dashboard. With nothing waiting, the reply says so.

## 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.

The target must be a memory the AI tool may change: your own, and on Small Business and Enterprise not shared. Otherwise nothing is saved and the call is refused with error `-32602`, naming the reason (`relation_target_not_its_owner`, `relation_target_shared` or `relation_target_waiting`) as in Save. Links of the other types are not limited this way.

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 with `korium_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 own `supersedes` link.

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's `relation_type`.
- `wrong_type`: they are related, but not like that. Send the link's `relation_type`, and `corrected_type` with 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_id` is the memory and `to_id` is the person it is linked to now. For a name the memory shows in `unclear_people`, `to_id` is any one of the `person_ids` listed there. Name the right person with `right_person_id`, or send `new_person: true` when 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, send `right_thing_id` with the ID of the stored thing it really is, or `not_a_person: true` when there is no stored thing to name. Send exactly one of the four. `relation_type` is not needed. Only the memory's owner may send `wrong_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 `-32602` and 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. An AI tool can bring back only a memory it may change (your own, and on Small Business and Enterprise not shared); for any other, the reply says the memory stays retired. 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.

## Delete a memory: korium\_delete\_memory

Deletes one of your own memories. It takes one field, `memory_id`, the 36-character ID of the memory.

```
{
  "memory_id": "<memory-id>"
}
```

The memory no longer shows in search, in AI tools or on the Memories screen. Korium keeps it rather than erasing it. On Small Business and Enterprise, where memories are shared by hand, an AI tool deletes only a memory that is not shared. On Team, where every memory is shared with the team, it deletes your own memories.

When the memory is deleted, the reply has `deleted` (the ID), `marked` (how many stored records were marked deleted) and a `message`. Otherwise nothing changes and the call is refused with error `-32602`. The `reason` says why: `memory_not_its_owner` (the memory is someone else's), `memory_shared`, `memory_waiting` (a request is waiting on it), `memory_not_found` (no memory with that ID that you can read) or `memory_still_saving` (it was saved moments ago and is not filed yet; send the delete again in a few seconds). A shared memory, or one with a request waiting, is deleted on the dashboard instead.

## 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. Without `trunk`, the first branch a repository ever indexes becomes its trunk, and later uploads that don’t name it never move it. korium-cli sends `trunk` only 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_repositories` lists 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.

[All Korium guides](index.html) [Next: Troubleshooting](troubleshooting.html)

Use the [setup checklist](release-status.html) before changing an automated workflow. Check your installed client’s help if its interface differs.
