ai-agent-foundationrun.md00_intake/stub.mdpackages/services/src/ai/ today is a config folder plus one bespoke agent (brd/) with its own prompt/tools wiring. Every new AI feature would re-invent agent setup, tool definitions and streaming plumbing. The epic's explicit requirement is a strong foundation that lets AI seamlessly connect to the application — the onboarding agent is the first consumer, not a one-off.
Rework packages/services/src/ai into a small reusable agent core: a conventional way to define an agent (model/config via the existing AI Gateway setup), a tenant-scoped tool registry so tools wrap existing domain services (products, services, skills/categories, SLA, tenant settings) and can never reach across tenants, structured-output helpers for typed generation, and the streaming route + human-in-the-loop conventions consistent with ai/brd and the ai-feature skill. Deliverable is the core + at least one thin proof (e.g. a read-only catalogue tool) demonstrating a service exposed as an agent tool. brd/ keeps working; migrating it onto the core is optional and only if trivially cheap.
brd/.This is the epic's foundation bet — size it deliberately: general enough that stubs 4–5 and future agents (matching, success, recruiter are roadmapped) sit on it, small enough to stay one PR. If Define finds it ballooning, cut the proof-tool scope, not the tenant-scoping. touches: packages/services/src/ai/ (respect the /server vs /shared entry-point boundary per services-imports). Vercel AI SDK v6 (ToolLoopAgent) per the ai-feature skill.
01_define/output/spec.md./ai entry)packages/services/src/ai/ today is a config folder (config/) plus one bespoke agent (brd/)
that hand-wires its own prompt, tools and streaming plumbing. Every new AI feature would re-invent
that setup — agent declaration, tool definitions, structured output and the human-in-the-loop
streaming conventions — and nothing today forces a tool to stay inside its tenant. The
tenant-onboarding-wizard epic (Build the Bridge / Q2-2026 O1 — establish PMF with vendor partners,
KR: onboard 8+ vendors onto paid tiers) explicitly requires a strong foundation that lets AI
seamlessly connect to the application before the onboarding concierge (and the roadmapped
matching / success / recruiter agents) are built on top. This is the epic's foundation bet: general
enough to carry stubs 4–5 and future agents, small enough to stay one PR.
Rework packages/services/src/ai into a small, reusable agent core, keeping the existing AI Gateway
config and the Vercel AI SDK v6 (ToolLoopAgent) idiom established by ai/brd and the ai-feature
skill:
brd/.ai/brd and the ai-feature skill, rather than being
re-hand-rolled per feature.brd/ keeps working unchanged. The core is exported through the existing @sustentus/services/ai
entry point, respecting the /server vs /shared boundary (per the services-imports skill).
brd/.@sustentus/services/ai and respects the /server vs /shared
import boundary (no server-only code leaks into a client-safe entry point).apps/web chat surface or route wiring this run.brd/ onto the new core — brd/ stays as-is; a later run may adopt the core if it is
trivially cheap.02_build/output/notes.mdfeat: ai-agent-foundation — reusable tenant-scoped agent core + catalogue proof toolpackages/services/src/ai/core/agent.ts (new): defineAgent({ model?, instructions, tools? }) —
declares a ToolLoopAgent with the house gateway-model default (DEFAULT_AGENT_MODEL) and the AI
SDK cast, so a new agent needs no copy of brd/.packages/services/src/ai/core/structured-output.ts (new): generateStructured({ schema, prompt, system?, model? }) — Zod schema in, validated typed object out, via the AI Gateway (generateObjectgateway(...)). No raw provider SDK.packages/services/src/ai/core/tenant-tools.ts (new): the tenant-scoped tool registry.
createTenantContext(tenantId) validates the id (24-char ObjectId) and freezes it; defineTenantTool
builds a tool whose execute closes over the bound context; buildTenantTools(ctx, factories) binds a
named set to one tenant. Tool input schemas never carry a tenantId, so a model/user cannot direct a
tool at another tenant — ctx.tenantId is the only tenant id a tool ever sees.packages/services/src/ai/core/index.ts (new): core barrel.packages/services/src/ai/tools/catalogue.ts (new): read-only proof tool. list_catalogue wraps the
existing tenant-scoped productService/serviceService (findAll(ctx.tenantId, …)) and returns the
tenant's products/services. buildCatalogueTools(ctx) binds it, ready for defineAgent.packages/services/src/ai/index.ts (edit): re-export the core via @sustentus/services/ai (client-safe
— core imports only ai + zod, no DB).packages/services/src/server/index.ts (edit): export catalogueTool / buildCatalogueTools from
@sustentus/services/server — the DB-touching proof tool lives behind /server, not /ai.inputSchema is business input only;
the tenant id is supplied by the bound TenantContext, never by the caller. There is no code path that
lets a tool receive a different tenant's id, so the "can't cross tenants" guarantee holds by
construction./ai vs /server split (AC6). The generic core (no DB) is exported from the client-safe /ai
entry that agents already use; the concrete catalogue tool imports Mongoose-backed services and is
therefore exported from /server only. No server-only code leaks into /ai.mongoose.Types.ObjectId,
so the core can live in the client-safe barrel.brd/ untouched (migration out of scope per the spec).defineAgent (no brd/
copy needed).createTenantContext + defineTenantTool/buildTenantTools; the catalogue tool only ever reads ctx.tenantId.generateStructured.catalogueTool / buildCatalogueTools over productService/serviceService.brd/ and its exports are untouched; only
additive exports were added to the barrels.@sustentus/services/ai, DB tool from @sustentus/services/server — /server
vs client-safe /ai boundary respected.tenantId — worth a look
when future tools are added (a tool that accepts a tenantId in its schema would break the invariant).stubs 4–5 (source-analysis, onboarding concierge) build their tools on defineTenantTool and their
agent on defineAgent; the catalogue tool is the template to copy for the remaining services (skills,
SLA, tenant settings).03_release/output/investor-update.mdWho it's for: Internal platform — every AI agent we build, now and roadmapped. What shipped: A reusable agent core: any feature declares an AI agent and reaches tenant data through shared, tenant-isolated tools. Why it matters: Faster, safer delivery of the onboarding concierge behind our Q2 objective — establish product-market fit with vendor partners.
Tools bind to one tenant by construction; cross-tenant access is structurally impossible.
Dig deeper: https://github.com/sustentus/sustentus/pull/604
03_release/output/release.md205d589, 2026-07-07apps/docs/app/technical/packages/services/page.mdx (AI section — agent core/ai vs /server split) in this PRToolLoopAgent, generateObject, tool) verified
against installed ai@6.0.175 + zod@3.25.67 declarations — all PASS; each mirrors an
already-compiling reference file (brd/). CI typecheck (Quality Project) green confirms.ctx.tenantId is the only tenant a tool sees.defineAgent, no brd/
copy.createTenantContext +
defineTenantTool/buildTenantTools; catalogue tool reads only ctx.tenantId.generateStructured.catalogueTool / buildCatalogueTools over productService/serviceService.brd/ untouched; only additive barrel exports.@sustentus/services/ai; DB tool from @sustentus/services/server — boundary
respected.