← Missions

Senior TypeScript: Make Our AI Agent Multi-Tenant and Production-Ready

Budget: $4000.0 FIXED / ⭐ 0.00 (0) FRA

typescript, postgresql, node.js, next.js, docker

Qualifications préférées

  • Localisation : India
  • Expérience : Expert
  • Job Success : 90%+
  • Rising Talent préféré
  • Gains min. : $10,000+
THE PRODUCT We sell B2B prospecting. A user describes who they want to reach — "e-commerce shops in Lyon running Shopify, find the owner's email, write a first outreach email" — and an AI agent actually goes and does it: it searches real data sources, builds a table of leads, enriches it column by column, writes the email sequence, and schedules the campaign to go out from the user's own mailbox. We have this in two separate applications, and they need to become one. APP A — the commercial platform, in production NestJS, PostgreSQL, Prisma, BullMQ, deployed with Docker behind nginx. It handles everything around the product: user accounts, Google and Microsoft OAuth, Stripe subscriptions and a credit ledger, connected mailboxes, real email sending through the Gmail API and Microsoft Graph with sending windows and per-mailbox caps, inbound reply detection, and an admin back office. It works. What it does not have is any intelligence: today the user fills in a six-step form. APP B — the agent, built separately Fastify 5, Prisma 6.19, SQLite, with its own Next 16 / React 19 front end. This is the product we actually want to sell: a chat where the user writes what they want, and an agent loop with 14 tools goes and does it. Eleven live data connectors, a code-execution sandbox for enrichment logic, a spreadsheet-style table UI, a campaign builder. It works too — but it was built as a single-user prototype and it has never left a laptop. THE WORK Move App B onto App A's infrastructure, so one logged-in customer can use the agent as the product. 1. Database. App B runs on SQLite. Move it to App A's PostgreSQL, in its own schema. There is no raw SQL anywhere in it and no dialect-specific query, so this is a provider change plus a first migration — the schema has never had one. 2. Authentication. App B has none. Put its routes behind App A's existing session token, and keep its live chat streaming (SSE) working through nginx — it works in development and dies behind a default proxy configuration. 3. Deployment. The server has no Dockerfile and no health endpoint. The front end has a Dockerfile, but it was inherited from a template and cannot build — it copies a standalone output our config explicitly disables. Build both images properly, add the services to the existing compose stack, route them under /agent/ on App A's domain, and give us a status page a non-developer can read. 4. Multi-tenancy. This is the largest part and the one that matters. App B was written for exactly one user: 73 of its 77 routes are open, 42 answer anyone holding an id, 366 database calls filter on nobody, and the email sender identity is a hardcoded string. Six uniqueness constraints are global, so two customers cannot both have a document at the same path or exclude the same domain — and one settings row is keyed to the literal string "global". Spending is metered into a single platform-wide pot with no user attached, so it has to become per-customer and land on the balance that customer actually bought. It also has a tool that runs model-written code with database access, so route-level filters alone do not cover any of this. 5. The screens. Closing those routes breaks the app unless the front end changes with it: there is not a single Authorization header anywhere in it, and its sign-in page is a link that walks straight into the dashboard under the words "simulated local sign-in". Merge its 48 files into App A's Next front end — same Next 16, same React 19, same Tailwind 4, same tsconfig, so this is a move plus one shared HTTP layer, not a rewrite. Present the token, guard the routes, refresh the session, and keep the live chat stream authenticated — EventSource cannot carry a header, so that one needs a deliberate answer. Without this, nothing above can be accepted: the acceptance test is two customer accounts open side by side in two browser windows, and neither of them can log in today. 6. Make it fast, and make sure it actually works. The agent is slow: a single turn can run five to ten minutes, it re-reads documentation it already holds in context, and it makes model calls it does not need. Nothing in the codebase measures any of that today — no timing, no token counting, no per-turn record — so the first task here is building that instrument, and then cutting what it shows: repeated reads, redundant calls, work done one after another that could run at the same time. Then go through the wired capabilities, find the ones that are broken or return something other than what they claim, and fix them. We agree the targets and the list with you before you start, and at the end we compare against your own baseline, not against a feeling. THE PRODUCT We sell B2B prospecting. A user describes who they want to reach — "e-commerce shops in Lyon running Shopify, find the owner's email, write a first outreach email" — and an AI agent actually goes and does it: it searches real data sources, builds a table of leads, enriches it column by column, writes the email sequence, and schedules the campaign to go out from the user's own mailbox. We have this in two separate applications, and they need to become one. APP A — the commercial platform, in production NestJS, PostgreSQL, Prisma, BullMQ, deployed with Docker behind nginx. It handles everything around the product: user accounts, Google and Microsoft OAuth, Stripe subscriptions and a credit ledger, connected mailboxes, real email sending through the Gmail API and Microsoft Graph with sending windows and per-mailbox caps, inbound reply detection, and an admin back office. It works. What it does not have is any intelligence: today the user fills in a six-step form. APP B — the agent, built separately Fastify 5, Prisma 6.19, SQLite, with its own Next 16 / React 19 front end. This is the product we actually want to sell: a chat where the user writes what they want, and an agent loop with 14 tools goes and does it. Eleven live data connectors, a code-execution sandbox for enrichment logic, a spreadsheet-style table UI, a campaign builder. It works too — but it was built as a single-user prototype and it has never left a laptop. THE WORK Move App B onto App A's infrastructure, so one logged-in customer can use the agent as the product. 1. Database. App B runs on SQLite. Move it to App A's PostgreSQL, in its own schema. There is no raw SQL anywhere in it and no dialect-specific query, so this is a provider change plus a first migration — the schema has never had one. 2. Authentication. App B has none. Put its routes behind App A's existing session token, and keep its live chat streaming (SSE) working through nginx — it works in development and dies behind a default proxy configuration. 3. Deployment. The server has no Dockerfile and no health endpoint. The front end has a Dockerfile, but it was inherited from a template and cannot build — it copies a standalone output our config explicitly disables. Build both images properly, add the services to the existing compose stack, route them under /agent/ on App A's domain, and give us a status page a non-developer can read. 4. Multi-tenancy. This is the largest part and the one that matters. App B was written for exactly one user: 73 of its 77 routes are open, 42 answer anyone holding an id, 366 database calls filter on nobody, and the email sender identity is a hardcoded string. Six uniqueness constraints are global, so two customers cannot both have a document at the same path or exclude the same domain — and one settings row is keyed to the literal string "global". Spending is metered into a single platform-wide pot with no user attached, so it has to become per-customer and land on the balance that customer actually bought. It also has a tool that runs model-written code with database access, so route-level filters alone do not cover any of this. 5. The screens. Closing those routes breaks the app unless the front end changes with it: there is not a single Authorization header anywhere in it, and its sign-in page is a link that walks straight into the dashboard under the words "simulated local sign-in". Merge its 48 files into App A's Next front end — same Next 16, same React 19, same Tailwind 4, same tsconfig, so this is a move plus one shared HTTP layer, not a rewrite. Present the token, guard the routes, refresh the session, and keep the live chat stream authenticated — EventSource cannot carry a header, so that one needs a deliberate answer. Without this, nothing above can be accepted: the acceptance test is two customer accounts open side by side in two browser windows, and neither of them can log in today. 6. Make it fast, and make sure it actually works. The agent is slow: a single turn can run five to ten minutes, it re-reads documentation it already holds in context, and it makes model calls it does not need. Nothing in the codebase measures any of that today — no timing, no token counting, no per-turn record — so the first task here is building that instrument, and then cutting what it shows: repeated reads, redundant calls, work done one after another that could run at the same time. Then go through the wired capabilities, find the ones that are broken or return something other than what they claim, and fix them. We agree the targets and the list with you before you start, and at the end we compare against your own baseline, not against a feeling. This part needs no AI or model experience, and we are not looking for a prompt engineer. The whole model surface is a single 101-line file around `new Anthropic()` — no chains, no vector store, no embeddings. It is profiling, caching, concurrency and bug-fixing on a TypeScript codebase that happens to be an agent. What the agent decides and how it writes — its prompts, its skills, its playbooks — stays with us. That is product judgement, not contract work, and we are not going to ask you to guess at it. 7. Pricing. The two applications do not sell the same thing, and once the agent is the product only one of them can be right. App A charges 5 credits per prospect, in whole numbers — `CreditGrant.amount` is an integer column, so a 0.5-credit charge cannot even be written to it. App B charges per service call in fractions: 0.1 to write a message, 0.75 for a maps lookup, 3 for an email, 15 for a phone number, across 48 priced services. So sourcing becomes the thing customers pay for and sending becomes free, which is the opposite of today. What we need from you: make the ledger able to hold what the agent actually spends, retire the per-prospect charge, and make the plans and the billing screens tell the customer the truth. The twelve plans live in one config file and their Stripe price IDs are environment variables; creating the products and setting the amounts in Stripe is our job, not yours. Deciding what a lead should cost is also ours — we will hand you the grid.
Ouvrir sur Upwork

AI proposal draft

Generate a short cover letter for this job. Edit before sending.

Sign in to generate an AI proposal draft.

Connexion