All articles

Building a demo seed CLI in Go: 5 industry verticals, 5,000 documents, and realistic org data

Suman Akkisetty
Founder, KaryoSpace
April 2026
8 min read

Demoing an enterprise workspace with empty data kills the impression. Every module looks skeletal. The AI has nothing to reason about. The calendar is blank. The incident dashboard shows zeros. To solve this, KaryoSpace ships a standalone seed CLI that pre-populates realistic, interconnected data across five industry verticals in minutes.

Why a standalone binary

The seed tool lives at backend/go/cmd/seed/ and compiles to a separate binary from the main app. It imports the same store packages but has no HTTP server, no auth, and no WebSocket layer. This separation is intentional: the seed tool runs as a one-shot CLI command, not as a service, and it should never be accidentally compiled into the production binary.

// Usage
./seed --domain=insurance  --action=seed  --user=admin@karyospace.com
./seed --domain=banking    --action=reseed --user=admin@karyospace.com --embed
./seed --action=clean-all

The --action=reseed flag wipes all seeded data for a domain and recreates it from scratch. This is the reset command before a demo: one line, under 30 seconds, and the environment looks like a fresh install with real data.

Five industry verticals

Each vertical has domain-specific employees, projects, incidents, and knowledge content that makes the AI sound credible when asked about domain topics. An insurance vertical has claims adjusters, underwriting projects, and incidents about policy management systems. A healthcare vertical has physicians, EHR migration projects, and HIPAA-related incidents.

var verticals = map[string]VerticalConfig{
    "insurance":     {Domain: "demo-insurance.com",    Industry: "insurance"},
    "banking":       {Domain: "demo-banking.com",      Industry: "banking"},
    "healthcare":    {Domain: "demo-healthcare.com",   Industry: "healthcare"},
    "manufacturing": {Domain: "demo-mfg.com",          Industry: "manufacturing"},
    "logistics":     {Domain: "demo-logistics.com",    Industry: "logistics"},
}

The fake domains (@demo-insurance.com etc.) ensure seeded employee email addresses never collide with real Okta login addresses. The admin who runs the demo logs in as their real @karyospace.com account. Seeded employees are org members in the data but not Okta users.

Data volume per domain

Per vertical:
  80  employees (dept-first social graph, 5-6 connections each)
  30  projects + 90 sprints + 540 tasks
  120 incidents + 360 update comments
  90  email threads (60% inbox, 40% sent) — attached to real user
  80  calendar events (past/current/future) — real user as attendee
  50  knowledge docs + 250 chunks (RAG-ready body text)
  8   public channels + ~120 DM pairs + organic messages
  50  notifications (30 unread, 20 read) — for real user

Total: approximately 5,200 documents per domain across all collections. The seed run takes about 90 seconds without embeddings, and about 8 minutes with Gemini embedding generation.

Social graph algorithm

Employees in the same department are most likely to DM each other and be on the same projects. The social graph uses a department-first algorithm: each employee gets connections to 3-4 colleagues in the same department, plus 1-2 cross-department connections chosen at random. This produces a realistic communication pattern where intra-department messages dominate but cross-department interactions exist.

func buildSocialGraph(employees []Employee) map[string][]string {
    graph := map[string][]string{}
    byDept := groupByDept(employees)

    for _, emp := range employees {
        deptPeers := byDept[emp.Department]
        // 3-4 connections within department
        sameDept := sampleN(deptPeers, 3+rand.Intn(2), emp.ID)
        // 1-2 connections across departments
        otherDept := sampleCrossDept(byDept, emp.Department, 1+rand.Intn(2))
        graph[emp.ID] = append(sameDept, otherDept...)
    }
    return graph
}

Safety flag

Every seeded document has a _seeded: true field and a _seed_domain field. The clean commands query by these fields, never by date range or count. This means a clean-all operation only deletes documents that were seeded — it cannot accidentally delete real user data even if run on a production instance with pre-existing content.

func seedIncident(inc Incident, domain string) error {
    inc.Extra = bson.M{
        "_seeded":      true,
        "_seed_domain": domain,
    }
    _, err := db.Collection("inc_incidents").InsertOne(ctx, inc)
    return err
}

func cleanDomain(domain string) error {
    filter := bson.M{"_seed_domain": domain, "_seeded": true}
    for _, coll := range seedCollections {
        db.Collection(coll).DeleteMany(ctx, filter)
    }
    return nil
}

Embedding flag

The --embed flag generates Gemini vector embeddings for all knowledge chunks and incident descriptions after seeding. This enables semantic search and RAG retrieval on the seeded data. Without --embed, the seed runs fast but the AI can only find content via MongoDB text search. With --embed, the AI can answer nuanced questions about the seeded incidents and documents.

The embedding step throttles at 1 request per second to stay within the Gemini free tier rate limit. At 250 chunks per domain, this takes about 4-5 minutes. The --action=embed flag runs embeddings standalone, useful if the initial seed succeeded but the embedding step was interrupted.

Get in touch

Questions about KaryoSpace?

Contact Us
Message sent!
We read every submission and will get back to you.