Agent frameworks

Python SDK

The Python SDK in vincs calls the Vinc HTTP API. Use it directly, or add the LangGraph or Microsoft Agent Framework adapter from the same package.

If the agent has to follow its role's contract (what it must never do, its gates, the tools it may not use), read it with prefetch (role mode). prefetch raises when Vinc cannot be read; get_context (context mode) returns None and the model call goes ahead without context. The table below compares them.

Install and connect

Requires Python 3.11 or later. This package also installs the vinc account command. See CLI installation for terminal use and the local runtime requirements.

This page describes vincs 0.1.5, which carries vinc-client 0.1.12. A feature that came after vincs 0.1.2 names the release it came with. The separate vinc-client package has the same methods under from vinc_client import VincClient.

pip install vincs
from vinc import VincClient

vinc = VincClient()                       # reads VINC_API_KEY
team = VincClient(space="<team id>")      # a team's graph instead of your personal one
ArgumentDefaultMeaning
api_keyVINC_API_KEYA member key starting with vinc_ro_ (read) or vinc_sk_ (read and write).
spacepersonalpersonal or a team id.
base_urlVINC_API_URL, then https://mcp.vincs.ioMust be https; plain http only for localhost.
timeout30.0Seconds per request.
transportNoneAn httpx transport, for tests or a proxy.
prefetch_concurrency8How many reads prefetch sends at once, from 1 to 8. With 1 they go one after another.
retry_for90.0Seconds to keep retrying while the hub is briefly down; 0 turns retries off. See When the hub is briefly down.

Use it as a context manager, or call close() when you are done. AsyncVincClient has the same methods, awaited, and closes with aclose().

Read-only and write keys

KeyWhat it can call
vinc_ro_Every read on this page, including operation_status and role_check.
vinc_sk_The reads, plus ingest, record_episode, write_package, delete, delete_edge, merge_nodes and doc_delete.

With a read-only key, every write method raises VincAuthError with code read_only_key before anything is sent. A dry_run preview of a delete or merge counts as a write, because the server ranks it with the change it previews. vinc.read_only tells you whether the client holds a read-only key.

Context for one model call

block = vinc.get_context("Why are our colour tokens stored as OKLCH?")
if block:
    system_prompt += "\n\n" + block

get_context returns a text block ready to append to a system prompt, or None when nothing matched. The block is fenced and marked as reference data, because text in the graph can be written by anyone with access to it. It cites node ids the model can quote back.

ArgumentDefaultMeaning
search_fallbackFalseWhen the lookup finds nothing or several nodes tie, widen with a search (a second call).
max_charspackage defaultCap on the block's length.
max_excerpts3Excerpts per node.
min_query_chars3Shorter queries return None without a call.

If Vinc is unreachable or the daily limit is used up, it logs a warning and returns None, so your model call goes ahead. A key or space problem raises.

Reading

MethodParametersWhat it returns
whoami·The key's owner, plan and the teams it can reach.
briefquery, max_excerpts, historical, mode, as_of, known_atOne node that answers the question (or an id), with excerpts and links. status is resolved, choose or none.
searchquery, limit, cursor, detail, historical, mode, as_of, known_atText and meaning search, paged with cursor. detail is summary or full.
recalldomain, tags, limit, cursorThe episode timeline, newest first.
nodenode_idOne node by id. Raises VincNotFound if it does not exist.
edgesnode or nodes, type, dir, limitA node's stored links with their direction and evidence. Pass exactly one of node and nodes. From vincs 0.1.4.
documentdoc_idA document's exact text with its content hash.
rolerole_id, pinA role's whole contract with its rev and hash. See Roles and their evidence.
role_checkrole_id, output, person_only_requests, self_authoredThe hub applies the role's checks to an output and answers pass, block or interrupt. A pass grants no permission. From vincs 0.1.4.
operation_statusoperation_idThe receipt of an earlier write. See Retry a write. From vincs 0.1.3.
get_contextquery, search_fallback, max_chars, max_excerpts, min_query_charsA block for one model call, or None. See above.
prefetch, prefetch_with_contractrole_id, task, max_chars, pinThe block a model reads before it answers as the role; the second also returns the contract it read. See Roles and their evidence.
get_package_contextpackage_id, space, max_charsA handoff package, checked again with this key. See Hand a result to another agent.

Every method also takes space= to use another space for that one call. brief and search take mode (general, current or historical), as_of and known_at to scope the answer in time; historical=True is the older way to include expired evidence.

Writing, on purpose

Nothing in the package writes because a conversation happened. Write with a vinc_sk_ key, after a person approved the result.

writer = VincClient(api_key=os.environ["VINC_WRITE_KEY"])
writer.record_episode(
    "Fixed contrast on the dark secondary button",
    summary="text-secondary now passes 4.5:1 on the dark surface.",
    about=["decision:tokens-are-oklch"],
)

record_episode reads every about id first, so a typo raises VincNotFound instead of creating an empty node. It also takes date, domain, tags and episode_id, and from vincs 0.1.4 create_only and operation_id, as write_package does.

ingest(nodes, edges) writes any nodes and links, merged by id. Properties you send replace the stored ones whole: read first and send the merged result, or, from vincs 0.1.3, put the node's expected_rev and a props_patch (a JSON Merge Patch) on it. If someone wrote in between, the whole write is refused. If part of the write was skipped or warned about, it raises VincPartialWrite; e.result holds what was stored and the warnings.

Write methodParametersWhat it does
ingestnodes, edges, create_only, operation_id, review_claimsWrites nodes and links, merged by id. With review_claims=True it also compares Record and Decision claims with the ones you saved.
record_episodetitle, summary, about, date, domain, tags, episode_id, create_only, operation_idOne episode about nodes that already exist.
write_packagequestion, conclusion, claims, produced_by, run_id, space, title, package_id, create_only, operation_idOne handoff package Record. space is required.
deletenode_id, dry_run, preview_digest, operation_idDeletes a node and its links.
delete_edgefrom_id, to_id, type, dry_run, preview_digest, operation_idDeletes one link.
merge_nodeskeep_id, drop_id, dry_run, preview_digest, operation_idFolds drop_id into keep_id.
doc_deletedoc_id, dry_run, preview_digest, operation_idDeletes one document.

When the hub is briefly down

The hosted hub can stop answering for a moment, for example while a new version is deployed. From vincs 0.1.4 the client retries those cases, so they read as a slower call rather than an error.

Retry a write

A write whose response was lost may or may not have landed. Give each logical write a fresh lowercase UUID as operation_id, and reuse it only to retry that write. ingest takes it from vincs 0.1.3; record_episode, write_package and the delete and merge calls from vincs 0.1.4.

import uuid
from vinc import VincUnavailable

op = str(uuid.uuid4())                                 # one id per logical write
try:
    writer.record_episode("Release checklist approved", about=["decision:release-plan"],
                          episode_id="episode:release-checklist-approved", operation_id=op)
except VincUnavailable:
    receipt = writer.operation_status(op)             # decide from the receipt, then retry with the same op

Delete and merge with a preview

delete, delete_edge, merge_nodes and doc_delete cannot be undone, so call each twice. With dry_run=True nothing changes and you get the complete preview with a preview_digest. Show it to a person, then apply with that digest. These calls come with vincs 0.1.4.

preview = writer.merge_nodes("concept:colour-tokens", "concept:color-tokens", dry_run=True)
# show the preview to a person, then apply exactly what they saw
writer.merge_nodes("concept:colour-tokens", "concept:color-tokens",
                   preview_digest=preview["preview_digest"], operation_id=str(uuid.uuid4()))

If the affected state changed after the preview, the apply call is refused instead of acting on something nobody saw. A preview that would be too large, or that touches a target the key cannot reach, is refused as well. Both calls need a write key; an operation_id on the apply call makes it safe to retry.

Roles and their evidence

role(role_id) reads a role's stored contract as it is now. prefetch(role_id, task) builds the block a model should see before it answers: the whole contract, the full text of each document the role says to read first, and Vinc's material on the task, with node ids the model can cite.

role = vinc.role("concept:agent-reviewer-judgement")             # id, props and the roles it works with
block = vinc.prefetch(
    "concept:agent-reviewer-judgement", "Review the proposed change.",
    max_chars=120000,
)

Prefetch reads the complete role contract and each required document by its declared document id on every call, without a cache. Each document must have complete text and a verified content hash; missing, partial or unverified documents raise prefetch_spec_unresolved before optional task lookup. Authentication and service errors propagate. The combined max_chars budget defaults to 24,000 characters. Large roles need a larger budget, such as the 120,000-character budget in this example. prefetch_budget_exceeded stops the read instead of truncating the contract or required documents. Your code picks the role and its budget. Helpers use max_chars for this same budget.

role() also returns the contract's rev and hash. To keep an agent on the contract you reviewed, pass that hash as pin to role, prefetch or prefetch_with_contract. When the role has changed, the call raises VincRoleChanged before any document is read, with the current id, rev and hash in details. Moving the pin to the new hash is your decision. The pin covers the role contract, not the text of the documents it lists. Over REST this is expected_hash on GET /v1/roles/{id}, which answers 409 role_changed. It comes with vincs 0.1.5.

pin = vinc.role("concept:agent-reviewer-judgement")["hash"]       # after you reviewed it
block = vinc.prefetch(
    "concept:agent-reviewer-judgement", "Review the proposed change.",
    max_chars=120000, pin=pin,
)

Role mode or context mode

In the framework adapters, role mode calls prefetch and context mode calls get_context.

ModeWhen Vinc is unreachable or the daily limit is used upHub calls per callTools the role disallowsPinning
Role mode (prefetch)Raises, with no retry. Any failed read raises the same way.1 for the role, 2 per required document (the document and its node), 1 brief and 1 search.prefetch_with_contract also returns the contract, and RoleToolPolicy applies its disallowed_tools to tools with exactly those names. The framework adapters do this for you.Optional, from vincs 0.1.5: with pin set to a contract hash from role(), a changed role raises VincRoleChanged before any document is read. Without it each call reads the role as it is now.
Context mode (get_context)Logs a warning and returns None, so the model call goes ahead without context. A key or space problem raises.1 brief, plus a search with search_fallback=True when it ties or finds nothing. None for a query shorter than min_query_chars.Not applied: there is no role.Not applicable: each call reads the graph as it is.

The SDK keeps nothing between calls; the framework adapters decide how often to call. prefetch_with_contract and RoleToolPolicy come with vincs 0.1.4.

Hand a result to another agent

Write one package Record with the question, the conclusion and each claim's evidence, and give the next agent its id. Passing only citation ids loses the conclusion.

handoff = writer.write_package(                        # vinc_sk_ key
    "When does the dashboard become stale?", "Keep the 36 hour threshold.",
    claims=[{"claim": "Stale after 36 hours.", "evidence_ids": ["decision:threshold"]}],
    produced_by="agent:analyst", run_id="analysis-1", space="personal",
)
block = vinc.get_package_context(handoff["package_id"], space=handoff["space"])   # next agent

The space must be named, and the evidence must already exist in it. Each read checks every evidence id again with the reader's key, so deleted or unreadable evidence is marked as not citable. Source text is never copied into the package. Use a new package id for each handoff.

Check an answer against a role

OutputGate checks a final answer against the role's gates, never_checks and person_only, outside the model. evaluate returns pass, block or interrupt; require_pass raises OutputGateStopped on the last two.

from vinc import OutputGate

gate = OutputGate(role["props"])
result = gate.evaluate(final_answer)
print(result.verdict)                                  # pass, block or interrupt

A claim that something ran needs a ToolProof built from a real tool call your host saw in this run; never build one from the model's output. A prose-only contract or an unknown check blocks. A pass releases the answer and nothing more: it does not run, permit or undo a tool. RoleOutput, prepare_person_only and resolve_person_only turn a person-only request into an approval bound to that exact draft; the framework packages wire them into their own approval steps.

Validate a review

parse_review checks a reviewer's JSON against ReviewOutput: a verdict of ACCEPT or RETURN, findings with citations, and person-only requests. RETURN needs findings and ACCEPT needs none; malformed JSON or extra fields raise. A valid shape does not prove the citations exist or that anyone approved.

Errors

ClassWhen
VincErrorThe base class. Every error below carries code, message, status, request_id, details and retry_after.
VincAuthErrorThe key is missing, malformed, refused, or read-only for a write. Fix the configuration.
VincNotFoundThe node, document or space does not exist for this key.
VincInvalidArgumentThe request was malformed or refused: a topic limit, a create_only id that exists, an expected_rev or preview_digest that no longer matches, or an operation_id reused with other arguments.
VincRoleChangedA pinned role has changed. No contract comes back; details holds the current id, rev and hash. It is a kind of VincInvalidArgument.
VincQuotaExceededThe daily limit is used up. retry_after is in seconds.
VincUnavailableNetwork or server failure, raised once the retries above have run out. For a write, check details["write_may_have_applied"] and operation_status before retrying.
VincPartialWritePart of a write was skipped or warned about. result holds what the server said.

Other public names

Everything below is importable from vinc, or from vinc_client in the separate package.

NameParametersWhat it is
format_contextbrief, search, max_chars, limitThe block get_context returns, built from a brief and the search that widened it.
format_answerbrief, search, max_chars, limitThe same block without the instruction to end with a Sources: line.
format_brief, format_searchbrief or result, max_chars, and limit for a searchOne brief or one search as a block.
fencebodyWraps text in a backtick fence longer than any backtick run inside it.
BOUNDARY, SOURCES_LINE·The sentence that marks a block as reference data, and the instruction to end every answer with a Sources: line.
sources_linetextThe ids on an answer's final Sources: line: [] when it names none, None when the line is missing.
build_episode_fragmenttitle, summary, about, date, domain, tags, episode_idThe fragment record_episode sends.
build_package_fragmentquestion, conclusion, claims, produced_by, run_id, title, package_idThe fragment write_package sends.
format_packagenode, space, readable_ids, evidence_nodes, max_charsA package Record as a block, with each claim marked citable or not.
check_package_handoffnode, space, gate, readable_ids, repositories, evidence_nodes, proofs, max_charsChecks an existing package with the producer's gate before you pass it on, keeping its conclusion and claims word for word.
OutputGateprops, extra_gates, self_authoredevaluate takes answer, proofs, person_only_requests, phase, manifest, resolved_sources, repositories and returns a GateResult (verdict, failed_checks, person_only_actions, evidence_sentences); require_pass raises OutputGateStopped instead. See Check an answer against a role.
ToolProofaction, target, call_id, success, claimA tool result your host saw in this run, passed to evaluate.
preserve_required_routebase, overrideRefuses a route override that drops or skips a required gate runner step.
RoleOutputtext, person_only_requestsThe answer shape a model returns when it may ask for a person-only action.
prepare_person_onlyoutput, gate, role_id, run_id, evidenceReturns the checked output, or an ApprovalRequest (request_id, role_id, run_id, output, actions) bound to that draft.
resolve_person_onlyrequest, decisionResumes the saved request with an ApprovalDecision (request_id, approved) and returns an ApprovalResult (status, output, actions).
ReviewOutput, Finding, PersonOnlyRequestverdict, findings, person_only_requests; a finding has what, cite, quote, rule; a request has action, reasonSee Validate a review.
parse_review, UnparsedReviewvalueChecks a reviewer's JSON against ReviewOutput. The framework review helpers raise UnparsedReview (status is UNPARSED) after their one retry.
RolePrefetchblock, contractWhat prefetch_with_contract returns.
RoleToolPolicyrole_id, disallowedfrom_contract(contract) reads a contract's disallowed_tools; allows(name), allowed(tools), unmatched(tools), warn_unmatched(tools) and refusal(name) apply them by exact name.