Technical guides

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.

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.

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": []
}
FieldHow to read it
schema_versionThe 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[].placesWhere 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[].idThe source memory. Keep it when checking a claim or making a correction.
thoughts[].final_scoreThe 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_partWhat 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_peoplePresent 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_countThe number of thoughts in this response, not a complete count of everything your question could refer to.
entities_partialTrue 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_inThe 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 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.

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:

ReasonWhat it means
repo_unknownThis account has no index for the repository.
branch_unknownNo index for that branch; branches lists the ones that exist.
index_update_in_progressAn index of that branch is being built right now. Try again after it finishes.
promote_requiredIn this account only someone who can publish the repository’s index may delete any of it.
delete_refused:last_branchIt is the repository’s only index.
delete_refused:trunkIt 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.

Use the setup checklist before changing an automated workflow. Check your installed client’s help if its interface differs.