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.
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.
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.
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.
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
}
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
}
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.
Questions about KaryoSpace?