Skip to content

Manage Resources โ€‹

Three tools follow the same action-discriminated "manage" pattern โ€” one tool, one action parameter, multiple operations (create, list, get, update, delete, assign, remove):

  • gnosari_manage_knowledge โ€” knowledge sources (this page's primary subject)
  • gnosari_manage_traits โ€” personality traits (briefly covered below)
  • gnosari_manage_data_collection โ€” data collection templates (briefly covered below)

See Tool Patterns for the general manage-tool action pattern shared by all three.


gnosari_manage_knowledge โ€‹

Create, list, get, update, delete, assign, or remove knowledge sources. Knowledge sources are documents or websites that give agents domain expertise via RAG.

Annotations: readOnlyHint: false ยท destructiveHint: false ยท idempotentHint: false ยท openWorldHint: true

This is the only manage_*-family tool with openWorldHint: true. Every other manage_* tool (gnosari_manage_traits, gnosari_manage_data_collection) is openWorldHint: false because they only touch Gnosari's own data. gnosari_manage_knowledge breaks that pattern specifically for action="create" with type omitted: the auto-resolve path makes outbound HTTP requests to an arbitrary caller-supplied site (probing for a sitemap) before any database write happens. That reaches beyond the server's own data boundary, so the annotation reflects it honestly.

Parameters โ€‹

NameTypeRequiredDefaultDescription
actionLiteral["create", "list", "get", "update", "delete", "assign", "remove"]Yes-Action to perform on knowledge sources
namestring | nullConditionalnullSource name โ€” required for create (even in auto-resolve mode; unlike the REST API, this tool never derives it from the hostname)
urlstring | nullConditionalnullSource URL โ€” required for create
typeLiteral["website", "sitemap", "discovery"] | nullNonullContent type for create. Omit it OR pass "website" for whole-site auto-resolve โ€” see below. "sitemap" / "discovery" select that loader explicitly and are used as-is with no probing
single_pagebooleanNofalsecreate only. Opt out of whole-site ingestion: load ONLY the given URL as a single page (data_type="website"), skipping the probe. Applies to omitted/"website" type; combining it with "sitemap"/"discovery" raises a conflict error
searchstring | nullNonullSearch query (list)
statusLiteral["unloaded", "loading", "loaded", "failed"] | nullNonullFilter by loading status (list)
source_idinteger | nullConditionalnullSource ID โ€” required for get / update / delete
source_idslist[integer] | nullConditionalnullSource IDs โ€” required for assign / remove
gnosari_idinteger | nullConditionalnullAgent ID โ€” required for assign / remove; optional on create to create-and-assign in one call

Returns โ€‹

KnowledgeResult โ€” one of:

ActionReturn type
create, get, updateKnowledgeSourceRead
listPaginatedKnowledgeSourcesResponse
deletedict[str, str] โ€” {"message": "Knowledge source {id} deleted"}
assign, removedict[str, str | list[int]] โ€” {"message": str, "knowledge_source_ids": list[int]}

The auto-resolve contract โ€‹

When action="create" and type is omitted (or explicitly None or "website") and single_page is not set, the server calls resolve_website_source(url) โ€” a standalone resolver that does no database work โ€” before opening the transaction, since it makes outbound HTTP requests. It probes, in order, stopping at the first candidate that yields at least one page:

  1. {url}/sitemap.xml
  2. {url}/sitemap_index.xml
  3. Each Sitemap: directive found in robots.txt, in the order they appear

A candidate "yields" when its <loc> count is greater than zero (namespace-agnostic parsing, gzip-decompression supported, one level of <sitemapindex> nesting followed). The first sitemap with >0 locations wins and the source is created with data_type="sitemap", paths=[thatSitemapUrl].

If nothing yields a usable sitemap, the source falls back to data_type="discovery", paths=[normalizedUrl] โ€” NeoReader's Discovery API crawls the whole site starting from that URL when loading runs.

Passing type="sitemap" or type="discovery" skips the probe entirely: the URL you passed is used as-is as paths=[url]. type="website" (like omitting type) auto-resolves the whole site. To load a single page only, pass single_page=true โ€” that stores data_type="website" with paths=[url] and no probe.

type omitted / "website"        โ†’ resolve_website_source(url) โ†’ sitemap (first working candidate) OR discovery (fallback)
type "sitemap" / "discovery"    โ†’ used as-is, no HTTP probe, no resolver call
single_page=true                โ†’ single-page website load, no probe
single_page=true + sitemap/disc โ†’ conflict error (single_page is a whole-site opt-out)

Invalid or blocked URLs โ€‹

Before probing, the resolver normalizes the URL (prepends https:// if no scheme is present) and validates it isn't SSRF-blocked โ€” internal, loopback, link-local, or cloud-metadata hosts are rejected, as is any scheme other than http/https.

If validation fails, gnosari_manage_knowledge raises a fixed, non-enumerating error:

URL not permitted โ€” provide a public http(s) site URL

This message is deliberately generic. It does not say why the URL was rejected (blocked scheme vs. internal IP vs. metadata host) because a caller who could distinguish those reasons could use this tool as a blind SSRF oracle โ€” probing for which internal hosts exist by reading the difference in error text. The real reason is logged server-side only, via structlog under the event knowledge_source_url_rejected, for operators debugging a legitimate rejected URL. This is a deliberate anti-enumeration design decision, not a gap to "improve" by making the error more specific.


Examples โ€‹

Auto-resolve (recommended default) โ€” omit type entirely:

python
result = gnosari_manage_knowledge(
    action="create",
    name="Product Documentation",
    url="https://docs.acme.com",
    gnosari_id=gnosari_id  # creates AND assigns in one call
)
print(result.data_type)  # "sitemap" if one was found, else "discovery"

Single page only โ€” opt out of whole-site ingestion with single_page:

python
result = gnosari_manage_knowledge(
    action="create",
    name="Pricing Page",
    url="https://acme.com/pricing",
    single_page=True,  # skip the probe, load exactly this one page
    gnosari_id=gnosari_id
)
print(result.data_type)  # "website"

List sources filtered by loading status:

python
failed = gnosari_manage_knowledge(action="list", status="failed")
for source in failed.data:
    print(f"{source.name}: {source.data_type} โ€” {source.paths}")

Create standalone, then assign to multiple agents:

python
src = gnosari_manage_knowledge(action="create", name="Shared KB", url="https://kb.acme.com")
gnosari_manage_knowledge(action="assign", source_ids=[src.id], gnosari_id=support_bot_id)
gnosari_manage_knowledge(action="assign", source_ids=[src.id], gnosari_id=sales_bot_id)

Remove and delete:

python
gnosari_manage_knowledge(action="remove", source_ids=[src.id], gnosari_id=support_bot_id)
gnosari_manage_knowledge(action="delete", source_id=src.id)

Common Errors โ€‹

ErrorCauseSolution
ValueError: "name is required for create"name omitted on action="create"name is always required, even in auto-resolve mode
ValueError: "url is required for create when auto-resolving the whole site (omit type or use type='website'; pass single_page=true for a single page)"url omitted while auto-resolving (type omitted or type="website", single_page not set)Pass url, or pass type="sitemap"/"discovery" with url, or set single_page=true with url
ValueError: "URL not permitted โ€” provide a public http(s) site URL"URL is SSRF-blocked (internal/loopback/link-local/metadata host) or uses a non-http(s) schemeUse a public, internet-reachable http(s):// URL. Check server logs (knowledge_source_url_rejected) for the specific reason if you operate the server
ValueError: "source_id is required for get/update/delete"Missing source_id on those actionsPass source_id
ValueError: "source_ids and gnosari_id are required for assign/remove"Missing either param on assign/removePass both source_ids (list) and gnosari_id

gnosari_manage_traits (brief) โ€‹

Create, list, get, update, delete, assign, or remove personality traits โ€” the behavioral shaping layer for how an agent communicates.

Annotations: readOnlyHint: false ยท destructiveHint: false ยท idempotentHint: false ยท openWorldHint: false

NameTypeRequiredDescription
actionLiteral["create", "list", "get", "update", "delete", "assign", "remove"]YesAction to perform
name, description, instructionsstring | nullRequired together for createTrait identity and behavioral instructions
weightfloat | nullNoInfluence weight 0.0โ€“10.0 (default 1.0)
is_defaultbool | nullNoAuto-assign to newly created agents in this account
searchstring | nullNoSearch query (list)
trait_idinteger | nullConditionalRequired for get / update / delete
trait_idslist[integer] | nullConditionalRequired for assign / remove
gnosari_idinteger | nullConditionalRequired for assign / remove, or optional on create to assign immediately
python
trait = gnosari_manage_traits(
    action="create",
    name="Empathetic",
    description="Warm, understanding tone",
    instructions="Acknowledge the user's feelings before offering solutions.",
    weight=2.0,
    gnosari_id=gnosari_id  # creates AND assigns
)

gnosari_manage_data_collection (brief) โ€‹

Create, list, get, update, delete, assign, or remove data collection templates โ€” what structured data agents extract from conversations. Also toggles per-agent file-upload capability via enable_attachments.

Annotations: readOnlyHint: false ยท destructiveHint: false ยท idempotentHint: false ยท openWorldHint: false

NameTypeRequiredDescription
actionLiteral["create", "list", "get", "update", "delete", "assign", "remove"]YesAction to perform
name, descriptionstring | nullConditionalTemplate identity (create/update)
fieldslist[FieldInput] | nullConditionalField definitions (create/update)
identity_fieldstring | nullNoName of the field to use as the person's identity/display name (create/update). Must name one of this template's fields. On update, omit to leave unchanged โ€” see clear_fields to remove the designation
clear_fieldslist["identity_field"] | nullNoFields to reset to null on update. Currently only "identity_field" is supported. Passing a field here AND as a value in the same call is rejected
searchstring | nullNoSearch query (list)
include_systemboolNoInclude system templates in list (default true)
template_idinteger | nullConditionalRequired for get / update / delete / assign / remove
gnosari_idinteger | nullConditionalRequired for assign / remove, create+assign, or enable_attachments
enable_attachmentsbool | nullNoTurn file uploads on/off for the agent's chat (requires gnosari_id); null leaves unchanged
collection_modestring | nullNopassive, opportunistic, active, or guided
custom_promptstring | nullNoRequired when collection_mode="guided" (max 5000 chars)
python
template = gnosari_manage_data_collection(
    action="create",
    name="Lead Info",
    fields=[{"name": "email", "field_type": "email", "required": True}],
    identity_field="email",
    gnosari_id=gnosari_id,
    collection_mode="active"
)

Clear the identity designation โ€” revert to server heuristic resolution without touching any other field:

python
gnosari_manage_data_collection(
    action="update",
    template_id=template.id,
    clear_fields=["identity_field"]
)

Next Steps โ€‹