Skip to main content
Sonzai Docs
Tutorial·~15 min

Resource Inventory + Knowledge Base

Track what tools, licenses, and subscriptions each user has and enrich them with live cost data from your Knowledge Base. By the end you'll have an agent that can tell a user their total subscription spend, cost changes, and what alternative solutions to consider — all grounded in real data.

What you'll build

  • → A Knowledge Base schema for software_license entities with market_price and tier info
  • → A cost-sync pipeline that pushes live pricing data into the KB via bulkUpdate
  • → An agent with inventory capability that auto-tracks assigned tools during conversation
  • → A portfolio query that joins user assignments with current KB pricing data

1. Define an Entity Schema

Schemas tell the KB what fields to extract and index for each entity type. Create one for software_license so the platform knows how to store and search your license data.

import { Sonzai } from "@sonzai-labs/agents";

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const PROJECT_ID = "proj_abc123";

await client.knowledge.createSchema(PROJECT_ID, {
  entity_type: "software_license",
  display_name: "Software License",
  fields: [
    { name: "market_price",  type: "number",  indexed: true  },
    { name: "tier",          type: "string",  indexed: true  },
    { name: "category",      type: "string",  indexed: true  },
    { name: "license_type",  type: "string",  indexed: false },
    { name: "trend_30d",     type: "string",  indexed: false },
  ],
});

You only need to create the schema once. From that point on the platform validates and indexes every entity of that type.

2. Seed Initial Data

Insert your first set of entities using insertFacts. This is also how you load historical data before going live. Include relationships so the KB can surface alternative or complementary tool recommendations.

await client.knowledge.insertFacts(PROJECT_ID, {
  entities: [
    {
      type: "software_license",
      label: "Figma Enterprise",
      properties: {
        market_price: 75,
        tier: "Enterprise",
        category: "Design Tools",
        license_type: "per-seat-annual",
        trend_30d: "+5%",
      },
    },
    {
      type: "software_license",
      label: "Slack Business+",
      properties: {
        market_price: 12.50,
        tier: "Business",
        category: "Communication",
        license_type: "per-seat-monthly",
        trend_30d: "+3%",
      },
    },
    {
      type: "category",
      label: "Design Tools",
      properties: { vendor_count: 18, avg_seat_cost: 45 },
    },
  ],
  relationships: [
    { from_label: "Figma Enterprise",  to_label: "Design Tools", edge_type: "belongs_to" },
    { from_label: "Slack Business+",   to_label: "Communication", edge_type: "belongs_to" },
    { from_label: "Figma Enterprise",  to_label: "Slack Business+", edge_type: "commonly_bundled" },
  ],
  source: "seed_v1",
});

3. Keep Pricing Fresh with bulkUpdate

Run a cost-sync job on a schedule (e.g. daily cron) that fetches current pricing from your vendor data source and pushes it into the KB. bulkUpdate merges properties into existing nodes matched by label — no need to delete and re-insert.

// cost-sync.ts — run daily
import { Sonzai } from "@sonzai-labs/agents";
import { fetchLatestPricing } from "./vendor-api"; // your data source

const client = new Sonzai({ apiKey: process.env.SONZAI_API_KEY! });
const PROJECT_ID = "proj_abc123";

async function syncPricing() {
  const pricing = await fetchLatestPricing(); // [{ name, price, trend }]

  await client.knowledge.bulkUpdate(PROJECT_ID, {
    updates: pricing.map((license) => ({
      entity_type: "software_license",
      label: license.name,
      properties: {
        market_price: license.price,
        trend_30d: license.trend,
        last_synced: new Date().toISOString(),
      },
      // upsert: true — creates the node if it doesn't exist yet
      upsert: true,
    })),
  });

  console.log(`Synced ${pricing.length} license prices`);
}

syncPricing();

Batch size

Batches of ≤100 items are processed synchronously (immediate response). Larger batches are queued and processed asynchronously — the response includes a job ID you can poll for completion.

4. Enable Inventory on Your Agent

Enable the inventory and knowledge capabilities on your agent. This gives the agent the sonzai_inventory_update and sonzai_inventory tools automatically — no prompt engineering required.

const AGENT_ID = "agent_xyz";

await client.agents.updateCapabilities(AGENT_ID, {
  inventory: true,   // enables sonzai_inventory_update + sonzai_inventory tools
  knowledge: true,   // enables knowledge_search tool
  project_id: PROJECT_ID,  // which KB to join against
});

You can also set this from the dashboard: go to Agents → your agent → Capabilities and toggle Inventory on.

5. Let the Agent Track Resources in Conversation

Once inventory is enabled, the agent calls sonzai_inventory_update on its own whenever a user mentions a tool or subscription they use. You just chat normally — the platform does the KB resolution and storage.

// Your backend chat endpoint
for await (const event of client.agents.chatStream(AGENT_ID, {
  userId: "user_123",
  messages: [
    {
      role: "user",
      content: "We just provisioned 10 Figma Enterprise seats at $75/seat.",
    },
  ],
})) {
  // The agent streams its reply — and internally calls
  // sonzai_inventory_update({ action: "add", item_type: "software_license",
  //   description: "Figma Enterprise", properties: { plan: "Enterprise",
  //   purchase_price: 75, quantity: 10 } })
  // The platform resolves the KB node, stores the link, and the agent
  // continues the conversation without interruption.
  process.stdout.write(event.choices?.[0]?.delta?.content ?? "");
}

How KB resolution works: The platform searches the KB for the item description. If exactly one node matches, it links automatically. If there are multiple candidates, the response returns status: "disambiguation_needed" with a list of candidates so the agent can ask the user to clarify.

6. Query the Enriched Portfolio

Use mode="value" to get each user resource joined with the latest KB pricing data. The platform computes gain_loss automatically:(market_price - purchase_price) × quantity.

const portfolio = await client.agents.inventory.query(AGENT_ID, "user_123", {
  mode: "value",
  project_id: PROJECT_ID,
});

// portfolio.items:
// [
//   {
//     fact_id: "fact_abc",
//     item_label: "Figma Enterprise",
//     kb_node_id: "node_xyz",
//     user_properties: { plan: "Enterprise", purchase_price: 75, quantity: 10 },
//     market_properties: { market_price: 80, tier: "Enterprise", trend_30d: "+5%" },
//     gain_loss: 50,
//   },
// ]
// portfolio.totals: { "market_price:sum": 800, "*:count": 10 }

console.log(`Portfolio value: $${portfolio.totals?.["market_price:sum"]}`);
console.log(`Total cost change: $${portfolio.items.reduce((s, i) => s + i.gain_loss, 0)}`);

You can also use mode="aggregate" with the aggregations parameter to get portfolio-level totals without listing every resource — useful for organizations with many subscriptions.

// Aggregate: total count + total subscription cost, grouped by item_type
const agg = await client.agents.inventory.query(AGENT_ID, "user_123", {
  mode: "aggregate",
  aggregations: "market_price:sum,*:count",
  group_by: "item_type",
  project_id: PROJECT_ID,
});
// agg.totals: { "market_price:sum": 875, "*:count": 12 }
// agg.groups: [{ group: "software_license", values: { sum: 875, count: 12 } }]

7. Batch-Import Existing Subscriptions

If a user already has an existing set of subscriptions (from a CSV, a procurement system export, etc.), import them in bulk rather than waiting for the agent to discover each resource in conversation.

await client.agents.inventory.batchImport(AGENT_ID, "user_123", {
  item_type: "software_license",
  project_id: PROJECT_ID,
  items: [
    {
      description: "Figma Enterprise",
      properties: { quantity: 10, plan: "Enterprise", purchase_price: 75 },
    },
    {
      description: "GitHub Enterprise",
      properties: { quantity: 25, plan: "Enterprise", purchase_price: 21 },
    },
  ],
});

Up to 1,000 items per batch

The batch endpoint processes up to 1,000 items per call. For larger imports, split into multiple calls or use the CSV priming feature in the dashboard.

Next Steps

  • → Set up a recommendation rule in the KB to surface alternative tools to users
  • → Add trend tracking (7d/30d/90d) to power "biggest cost increases" reports
  • → View per-user inventory live in Dashboard → Agents → your agent → Users → select user → Inventory / Assets
  • → Read the Knowledge Base reference for schemas, analytics rules, and full-text search