# Digital Crew platform Digital Crew is an open platform for deploying specialised AI workers that share identity, workspaces, billing and governed access to company systems. Crew OS is the control plane; Max, Claire, Sophie and Kate are workers with their own product surfaces and APIs. This page is an orientation map. Each product guide names its verification date and known documentation limits. ## Choose your path | If you want to… | Start here | |---|---| | Run go-to-market work | [Max](/max) | | Research a market, company or prospect | [Claire](/claire) | | Operate recruiting workflows | [Sophie](/sophie) | | Research and execute SEO work | [Kate](/kate) | | Understand identity, workspaces and billing | [Crew OS](/crew-os) | | Integrate over HTTP | [Interactive API reference](/api) | | Give the docs to an LLM | [`llms.txt`](/llms.txt) or [`llms-full.txt`](/llms-full.txt) | ## Architecture ```mermaid flowchart TB User["Human or API client"] --> OS["Crew OS control plane"] OS --> Identity["Identity and workspace membership"] OS --> Billing["Token ledger and billing"] OS --> Max["Max · go-to-market"] OS --> Claire["Claire · research"] OS --> Sophie["Sophie · recruiting"] OS --> Kate["Kate · SEO"] Max <--> MCP["MCP tools and integrations"] Claire <--> MCP Sophie <--> MCP Kate <--> MCP ``` The workers are not merely pages inside one application. Each can have its own repository, database, deployment boundary and API, while Crew OS provides the shared commercial and identity layer. ## Repositories | System | Primary responsibility | Source | |---|---|---| | Crew OS | Website, identity handoff, workspaces, billing, token ledger and agent access | [`digitalcrew-orchestrator`](https://github.com/Digital-Crew-Technologies/digitalcrew-orchestrator) | | Max | CRM, sales execution, campaigns, conversations, meetings and GTM agent | [`max-agent`](https://github.com/Digital-Crew-Technologies/max-agent) | | Max MCP | Authenticated MCP adapter for the Max workspace API | [`max-mcp-server`](https://github.com/Digital-Crew-Technologies/max-mcp-server) | | Claire | Research Hub, scraping and normalisation APIs | [`claire-agent`](https://github.com/Digital-Crew-Technologies/claire-agent) | | Claire MCP | Research tools over MCP | [`claire-mcp-server`](https://github.com/Digital-Crew-Technologies/claire-mcp-server) | | Sophie | Recruiting workflows and APIs | [`sophie-agent`](https://github.com/Digital-Crew-Technologies/sophie-agent) | | Kate | SEO research and content operations | [`kate-agent`](https://github.com/Digital-Crew-Technologies/kate-agent) | | Docs | Human pages, Markdown/LLM surfaces and curated OpenAPI files | [`digitalcrew-docs`](https://github.com/Digital-Crew-Technologies/digitalcrew-docs) | ## Shared conventions ### Identity and tenancy Crew OS establishes user and workspace identity. A worker must still enforce its own tenant boundary. Browser sessions, public API keys, internal-service secrets, cron secrets and provider webhook signatures are different trust classes; a single "JWT protected" description is not sufficient. ### Billing Crew OS owns the shared token ledger. A worker may meter an external provider or action and debit workspace credits through the hub. Provider pricing and markup settings belong in server-side configuration, never client code. ### MCP MCP servers expose governed tools to compatible agents. Tool availability is not the same as authorization: each invocation must preserve user/workspace context and enforce the target system's permissions. ### Observability Deployments use service-appropriate tracing and error reporting. Logs and traces must not include access tokens, OAuth refresh tokens, unredacted secrets or unnecessary customer data. ## API documentation The [API explorer](/api) serves five selectable OpenAPI documents. These are curated integration references; product repositories remain authoritative when a spec lags current routes. Every guide links its raw OpenAPI file and states known coverage limits. ## Documentation for agents - `/llms.txt` is the compact discovery index. - `/llms-full.txt` concatenates the guide Markdown for retrieval or offline context. - `/content/.md` exposes each canonical guide as plain Markdown. - `/openapi/*.yaml` exposes machine-readable API contracts. All four surfaces are built from versioned repository files. An LLM should prefer current code and migrations over prose and should not treat dated audits or open pull requests as shipped truth. --- # Max > Current-state guide verified on 2026-08-26 against > [`max-agent@2e11856e`](https://github.com/Digital-Crew-Technologies/max-agent/commit/2e11856ee1e37a8c085d2f84136332f8787e7e34). > CRM authorization and credential-exposure statements were additionally > verified on 2026-08-27 against > [`max-agent@f44b685`](https://github.com/Digital-Crew-Technologies/max-agent/commit/f44b68507864f6af1accc6bd2bd26b0b6e1393bf). > The public Max MCP surface and its sourcing order were re-verified on > 2026-09-22 against > [`max-mcp-server@6000d0d`](https://github.com/Digital-Crew-Technologies/max-mcp-server/commit/6000d0d). > Campaign email threading was additionally verified on 2026-08-31 against > [`max-agent@1ebd1fd6`](https://github.com/Digital-Crew-Technologies/max-agent/commit/1ebd1fd6ec9ef9aedcb08d5cb040f3e541f5e669). Max is Digital Crew's AI-native go-to-market workspace. It brings CRM data, prospecting, campaign execution, conversations, sales actions, meeting intelligence and automations into one tenant-aware application. The Max agent can reason over that context and act through MCP tools. - Source: [`max-agent`](https://github.com/Digital-Crew-Technologies/max-agent) - MCP gateway: [`max-mcp-server`](https://github.com/Digital-Crew-Technologies/max-mcp-server) - Interactive API: [Max in the API explorer](/api#max) - Raw OpenAPI: [`max-agent-v1.yaml`](/openapi/max-agent-v1.yaml) - Raw Markdown: [`max.md`](/content/max.md) ## Coordinated Crew OS release > Prepared against the paired Crew OS and Max implementation branches on > 2026-09-08. The behavior below requires their migrations and coordinated rollout; > it has not been verified in production. The earlier verified capability map > remains historical release evidence. Crew OS becomes the authority for sign-in, workspace membership and connected account grants. Max keeps its sales execution and current conversation store. Both apps use the same menu editor, emoji picker, message composer and access editor, built from `packages/crew-core` in the Crew OS repository. A menu change is personal to the signed-in user and selected workspace; it never grants access. - Sign in to Max through the Crew OS handoff. The browser carries a short-lived, single-use code protected by PKCE; access and refresh tokens stay off redirect URLs. Max validates the exact active workspace before storing its independent session cookies. Signing out of Max ends that session. - Connect accounts in Crew OS or through Max's central connection flow. Grant campaign reading, direct-message reading and sending separately to an active teammate or named agent. Rights start off; grants can expire or be revoked. Workspace administration alone does not expose another person's messages. - Max API keys retain their scopes and current custom-role checks. Crew agent tokens can use only explicitly granted messaging operations and sources; they do not impersonate the human who issued them. Private chat and message restrictions still apply after source access is granted. - Crew OS and Max Unibox use the current Max conversation store. The shared composer supports email, LinkedIn and WhatsApp, emoji and up to five PNG, JPEG, GIF or WebP images within 4 MiB total. A thread's connected sender is fixed. Missing send permission disables sending; active automation requires the existing human-takeover confirmation. - Draft text and pending attachments remain with their user, workspace, sender and conversation. Sending in one thread cannot clear a newer draft elsewhere. Inbox imports resume from a server checkpoint after cancellation or timeout. Existing ambiguous account ownership is quarantined until an operator reviews an explicit account/workspace/owner mapping. The rollout does not rewrite historical conversation rows. See the paired repository runbooks for migration order, connection reconciliation and token reissue; do not enable the new consumer against an old authority. ## Personal appearance The shared appearance update adds controls in **Settings → Appearance** for comfortable/compact spacing, classic/glass primary buttons, reduced motion, and optional backgrounds: Pixel trail, Neon flow, Interactive gradient, Halftone trail, Liquid, Sonar grid, and Kinetic grid. Backgrounds start **Off**; when enabled, their intensity can be Low, Medium, or High. These new preferences apply to the signed-in user in this product on this browser. They are separate from workspace-wide branding and do not sync across devices or product domains. Existing theme, accent color, splash cursor and custom menu choices are retained. Selecting a background temporarily suppresses the splash cursor without changing its saved preference. The background layer does not intercept clicks or keyboard navigation. Effects pause in hidden tabs and outside the visible area; reduced motion shows a still version. Glass changes the primary button's surface, preserving its brand color and dimensions. **Reset appearance** resets only the new controls. Availability follows the paired Max and Crew OS appearance release; source PR approval alone does not establish production deployment. These seven effects are Digital Crew implementations inspired by the supplied examples, not the unavailable original registry components. ## Importing a large CSV > Verified on 2026-09-08 against > [`max-agent@b41a084`](https://github.com/Digital-Crew-Technologies/max-agent/commit/b41a084b31e194606fb65490233804e302312ae0). > This feature requires `20260908002050_durable_csv_imports.sql` and the configured > CSV processing schedule. This code check does not establish production rollout. CSV imports for People, Organizations, and Lists have no application-wide file size or row-count cap. Choose a file, review the sample, and map its columns. Max uploads and processes records in small batches and shows separate upload and import progress. Keep the page open until uploading finishes. Processing then continues in the background, and reopening **Import CSV** shows your recent imports. If uploading was interrupted, reselect the same file to resume with the saved mappings. Retry a failed import from its existing job so completed batches are preserved. Results distinguish new records, existing matches, and rejected rows. Review reported row failures and fix invalid values before importing those rows again. A single malformed or unusually large CSV record may need correction even though the file's total size is unrestricted. Available infrastructure and storage capacity still apply. The synchronous bulk-import APIs retain per-request bounds. Large browser imports use the separate resumable CSV job API documented in the [API reference](/openapi/max-agent-v1.yaml). ## Product map | Area | Shipped on the verified commit | |---|---| | CRM | People, organisations, lists, deals, multiple pipelines, tasks, activities, custom fields, saved views, ownership, sharing, full-text search, duplicate review and confidence-gated automatic merging | | Prospecting | ICP records, unified search/routing (GetLeads first, then Explorium), GetLeads, Explorium, Apollo, FullEnrich, Bouncer and ScrapeCreators integrations | | Campaigns | Visual workflows, lifecycle controls, schedules, account rotation, threaded email follow-ups, LinkedIn, WhatsApp and Lob physical-mail actions | | Conversations | Unibox for connected email and LinkedIn accounts, campaign conversation views, context panels and Inbox Autopilot | | Sales actions | Prioritised action/task execution, qualification inside meeting actions, supporting resources and Max-drafted work | | Meetings | Calendar sync, availability and booking, Vexa bots and transcripts, evidence-backed BANT qualification, conversation scorecards, trackers, talk ratios, objections, coaching and deal-risk signals | | Automation | Record, schedule, manual and inbound-webhook triggers; versioned definitions; waits, filters, CRM steps and outbound webhooks | | AI and crew | Streaming Max chat, built-in or workspace-hosted agent backend, MCP/provider tool discovery, agent prompts, knowledge, skills and memory | | Administration | Workspaces, invitations, custom roles, resource grants, API keys, notifications, auditing, token billing and observability | This is intentionally a capability map, not a count of feature folders or API routes. Counts drift quickly and can make documentation look precise while being wrong. ## What is not claimed - A provider enum, icon, package or roadmap mention is not proof of a working send path. Current connected messaging is centred on Unipile email, LinkedIn and WhatsApp; physical mail uses Lob. - The Max OpenAPI document is not exhaustive. It documents selected public operations; the application has a larger route surface. - Draft pull request [#782](https://github.com/Digital-Crew-Technologies/max-agent/pull/782) and stacked draft [#785](https://github.com/Digital-Crew-Technologies/max-agent/pull/785) propose a broader Cal.diy scheduling lifecycle and provider configuration. They are preview work, not part of the verified shipped state. - Dated files in `docs/plans/`, `docs/reviews/` and `docs/superpowers/specs/` are evidence and design context, not automatically current product truth. ## A typical workflow 1. Define an ICP and search across configured data suppliers. 2. Save people and organisations to the workspace CRM, enrich or verify them, and organise them into lists. 3. Create a visual campaign or work from prioritised actions and tasks. 4. Send through connected accounts while schedules, exclusions, limits and workspace permissions are enforced. 5. Handle replies in Unibox and attach conversation/activity history to CRM records and campaigns. 6. Book or join meetings, capture transcripts with Vexa, and inspect BANT qualification evidence, coaching, scorecards, trackers and deal-risk signals. 7. Ask Max to research, draft or act through the MCP tool catalogue using the caller's workspace context. ### Understanding the campaign queue Open a campaign's **Queue breakdown** to see its **Unfinished sequences**. **Eligible for worker check** and **Being processed** are subsets of that total; eligibility means the worker may check the sequence, not that a message is ready to send. **Last recorded wait reasons** groups work by its most recently recorded cause, such as sending hours, sender pacing or cooldown, limits, follow-up delays, event waits or retries. A sequence being processed can still show its previous reason. **Reason not recorded** means older or unevaluated work has no recorded cause. Pausing retains unfinished counts and recorded reasons. Checks resume when the campaign is active; paused campaigns show no work eligible for a worker check. **Next check** shows when the earliest sequence in a group may be checked, with the exact local time alongside it. Paused campaigns label it **Recorded check**. These times are not promised send times. Use **Refresh status** for a fresh view. ### Keeping campaign email follow-ups in one thread Each campaign email step can either start a new conversation or reply to the previous email sent to that person by the same campaign execution. The option is available in both the visual canvas settings and the linear sequence editor; cadence previews label reply-mode steps as **Same thread**. AI-generated sequences start the first email in a new thread and keep the second and later email touches in that thread by default. Existing campaigns retain their prior behavior: an email step without a threading choice starts a new conversation. If Max cannot find a safe prior campaign message, it sends the follow-up in a new thread. It never attaches the message to another campaign, person or sender account, and V1 does not reuse unrelated conversations that predate the campaign. ## Pipeline card selection (pending release) In Board view, **Select all (N)** beneath a stage header selects every person matching that stage and the active list/search, including cards not yet loaded. The stage checkbox and **Select loaded cards** select only loaded cards; scrolling does not select additional cards automatically. Whole-stage selection shows a loading state and can be cancelled. If a request fails or the stage changes during loading, the previous selection stays intact and an error invites you to retry. **Clear (N)** beneath a stage header removes that stage's selection. Individual card checkboxes can deselect people. Changing workspace, pipeline, list, search or view clears the selection. The bulk bar offers **Change stage**, **Add to list**, **Enrich**, **Merge** and **Delete** once loading completes. Stage changes retain normal rules and automations; failed moves can be retried separately. Clicking a card still opens the person, and dragging moves one card. ## Architecture and sources of truth Max is a Next.js 16 and React 19 application backed by two Supabase boundaries: - the Max project stores tenant-owned CRM, campaign, conversation, automation and meeting data; - the Crew OS admin project supplies shared identity/billing state and the token ledger. Product modules live mainly under `src/features/`; HTTP handlers live under `src/app/api/`. Feature folders represent several generations of structure, so maintainers should follow the nearest established pattern rather than assume a single universal layout. For current implementation questions, use this order: 1. Code and tests on the deployed/current branch. 2. `supabase/migrations/`, `vercel.json`, `package.json` and `src/lib/env.ts`. 3. Focused repository docs and runbooks. 4. This guide and the curated OpenAPI file. 5. Plans, dated reviews and pull requests. `supabase/db/schema-v3.sql` is a historical snapshot. Current database shape is the ordered migration history plus independently verified production state. ## Copy data to another workspace Available to signed-in users with active full membership and the required permissions in both workspaces. Existing Share and public links remain available. Use **Copy to workspace** from selected People or Organizations, selected pipeline cards, or **Copy with history** in a contact, company, list or campaign's Share dialog. Choose another workspace you actively belong to, then copy. Originals stay in the source. This supports preparing data in a personal workspace and bringing it into a company workspace. The destination's normal member permissions apply to the copies. Independent copies belong to that workspace and cannot be revoked from the source. The same Share dialog still offers its existing revocable shares and opt-in public links. Copying data does not turn on public access. For a single contact or company, open **Share**, find the destination under **Workspaces**, and click its **Copy with history** button. The destination is preselected; review it and confirm **Copy**. Later edits do not sync between workspaces. If copying is not enabled yet, the dialog explains that direct workspace sharing is unavailable. Contacts bring their linked companies, pipeline stages, history, profile hooks, meetings, transcripts, summaries, notes and recording files. Lists and campaigns bring their selected audiences; **Copy pipeline** brings its stages and all people, including those hidden by current filters. Campaigns arrive as drafts; hooks and queued enrichment are paused. Sending accounts must be connected separately. Live provider connections, private inbox contents, deal boards and active automation rules are not transferred. **Copy view** copies a saved filter/sort/column setup as a private destination view. To bring matching records too, select them in the table and choose **Copy view + N selected**. This copies the setup and selected records together; rows that are not selected are not included. Save unsaved view changes first. A view with workspace-specific references that are not included is rejected rather than broadened silently. Copies require full membership and the relevant read/write permissions in both workspaces. Copying custom-field definitions or coaching/analysis definitions requires a destination admin. Repeating a copy reuses records from that source without overwriting the destination team's edits. It does not merge independently created people by email. If a meeting is still recording or a file cannot be copied, the operation fails without publishing a partial set of records. Select at most 500 records of each kind per copy; split larger selections. Public links remain opt-in from **General access**. Signed-in viewers can import shared contact/company fields and campaign templates when they have permission to create those records in their destination workspace. Campaign template import includes authored messages and AI prompts; the public preview shows the sequence shape. Internal CRM notes, research, private history, recordings and live sender connections are not included in public imports. Shared meeting links retain their explicit report/transcript viewing scope. Existing **Share / Shared** controls still support revocable list/campaign shares. They check current source/destination access, as does taking a copy when leaving a workspace. Use **Copy with history** when you need independent records and their attached history/files. ## Authentication model Max does not have one universal API credential. | Caller | Typical credential | Important boundary | |---|---|---| | Browser user | Supabase session/JWT | Workspace membership, role and resource grants | | External API client | Scoped Max API key | Key scopes and bound workspace | | Crew OS/internal service | Route-specific shared secret | Only explicitly internal endpoints | | Vercel cron/worker | `CRON_SECRET` | Authenticate before claiming bounded work | | Provider webhook | Provider signature or secret | Verify the provider contract, then resolve tenant from stored integration state | | MCP client to Max MCP | Caller Bearer token (or a deployment-configured default) | Preserve the authenticated workspace on proxied API calls | Service-role Supabase clients bypass Row Level Security. Queries using them must be explicitly scoped; a client-supplied `workspace_id` is never sufficient authorization. Public CRM operations accept a workspace session or a Max API key with `crm:read` / `crm:write` as appropriate. Connecting or disconnecting HubSpot is restricted to a signed-in workspace admin. Max does not expose decrypted HubSpot access tokens: API and MCP callers invoke the scoped CRM operations, while provider credentials remain inside the server-side connection boundary. ## APIs The application API is primarily under `/api/v1`, with additional auth, webhook, worker and integration routes. Use the [interactive reference](/api#max) for curated public operations. The OpenAPI file should be used as an integration guide, not as an inventory of every internal route. If the spec and current code disagree: - code and route tests determine present behaviour; - the discrepancy should be fixed or recorded in the same change; - undocumented internal/provider routes should not be made public merely to increase coverage. ### Outbound webhooks Workspaces can create HTTPS webhook endpoints, subscribe them to supported events and inspect delivery attempts. Delivery is asynchronous, signed and retryable. Automation `send_webhook` steps also enforce outbound-network safety rules, including HTTPS and protections against private/metadata addresses and redirect-based SSRF. ### Duplicate automation The Duplicates page lets workspace admins enable automatic merging and choose an 80%–100% minimum confidence score (97% by default). The policy is off until an admin enables it. After the daily 03:30 UTC duplicate scan, Max processes a bounded batch of up to 10 qualifying pairs per workspace within a shared four-minute budget, keeps the older record, preserves prospect conflicts as alternates, keeps the winner's value for organization conflicts and records the irreversible operation in the audit log. Members can still review, merge or dismiss individual pairs manually. The scheduled route requires `CRON_SECRET`, scopes every service-role query by workspace and fails closed when the workspace policy cannot be read. ## Max agent and MCP The `/max` experience supports streaming conversation history and two agent backends: - the built-in serverless agent, using configured OpenRouter models and the available tool catalogue; - a workspace-hosted compatible agent backend. The chat composer accepts PNG, JPEG, GIF, WebP and PDF files up to 2.5 MB total per message. Files can be selected with the paperclip or dragged anywhere onto the open chat panel; Max stages valid files in the composer before send and explains unsupported or oversized drops immediately. ### Workspace agent control Settings → Agents exposes every registered model workload as a workspace agent. A workspace admin can pin an exact OpenRouter model, edit the agent's identity and knowledge, control its available skills where applicable, add to the shipped system prompt or replace that prompt completely. Changes apply to the next run and every workflow node assigned to that canonical agent; the GTM canvas links back to the agent's Workspace configuration rather than keeping a second copy. Complete replacement also removes any output schema, tool protocol, evidence rule, safety check or confirmation step carried by Max's shipped prompt. The editor shows that prompt, offers it as a starting point and warns the admin before replacement. Shared workspace house rules still apply. Ordinary members can read the workspace configuration and personalize supported agents under Mine, but their personal instructions remain additive and cannot choose the workspace's model, replace its prompt or re-enable an admin-disabled skill. `max-mcp-server` exposes the Max workspace API as MCP tools. It forwards the caller's Max Bearer token to the application API and never holds a credential of its own. Its catalogue covers every `/api/v1` operation that accepts a scoped workspace API key: 433 operations by default. Examples are campaigns, people, lists, organizations, deals, pipelines, automations, ICPs, calendars, meetings, the Unibox, enrichment, intent monitoring, CRM (HubSpot) and deliverability. They are published as 35 grouped domain tools, each taking an `action` argument. The following are not exposed: - operations that reject API keys (JWT-only, signed-in admin only, or human review, such as approving a task); - infrastructure routes, file transfers, and anything that returns a credential. The three operations that spend money (email warm-up and Mailpool orders) are only available when the MCP deployment sets `ENABLE_PURCHASE_TOOLS=true`. The server's `docs/ENDPOINT_COVERAGE.md` lists what is exposed and why the rest is left out. A Max API key reaches exactly one workspace. The `get_current_workspace` tool reports which one, so an assistant can say where it is about to write. To copy a prospect list into another workspace, share the list and import it from the link while signed into that workspace. When max-agent is briefly unreachable, the gateway answers `503` with `Retry-After` rather than `401`, so clients retry instead of dropping the connector. Tools that find new leads are ordered by cost: 1. `auto_create_prospect_list` and the company equivalents. Max runs GetLeads first and falls back to Explorium on its own. 2. The `getleads` group, when a provider is pinned. 3. The `explorium` group, for buyer intent, departments, revenue, keywords, tech stack or enrichments, or when GetLeads finds nothing. 4. The `apollo` group, as a last resort. Max's own chat leaves Apollo out. Max's application also discovers configured provider MCPs and built-in tools; that catalogue is resolved for the caller and may differ by workspace configuration, enabled skills and credentials. CRM tools use Max's scoped CRM operations; the application keeps the workspace's HubSpot credential server-side. Typical client connection settings are: ```json { "url": "https:///mcp", "headers": { "Authorization": "Bearer " } } ``` The adapter also supports a per-tool `bearer_token` argument and a deployment default, but passing the caller credential in the MCP request preserves the clearest request boundary. Do not put real tokens in documentation or source control. ## Integrations | Purpose | Providers used by shipped paths | |---|---| | Connected email, LinkedIn and WhatsApp | Unipile | | Prospect search/enrichment | GetLeads, Explorium, Apollo, FullEnrich | | Email verification | Bouncer | | Social/profile research | ScrapeCreators | | Mailbox/domain provisioning | Mailpool | | Physical direct mail | Lob | | Research worker | Claire API and MCP | | CRM/workspace connections | HubSpot and Notion OAuth paths | | Meetings/transcription | Calendar connections and self-hosted Vexa | | AI models | OpenRouter | | Tracing/errors | Langfuse and Sentry when configured | Optional integrations fail closed, return a clear unavailable response, or stay hidden when their configuration is missing. ## Credential access Mailbox password, app-password, two-factor and SMTP/IMAP credential export is restricted to an active human workspace owner/administrator with full access. Ordinary account reading and agent/API-key scopes cannot reveal those secrets. Order and mailbox responses use explicit public fields. Provider failures return safe connection diagnostics, including for previously stored errors; they do not return provider response bodies or credentials. Provider callbacks require their configured authentication; missing secrets return an unavailable response rather than accepting unsigned events. ## Shared task coordination Task details include shared Crew OS artifacts and human review. These records are available only after both services verify the current human and workspace. Max retains source execution: an artifact approval does not send a message or complete a domain action. The Crew OS Max tab uses the same task authority and version checks; it cannot impersonate an owner or bypass sender grants. The additive task outbox records committed revisions as decimal strings and deletion tombstones without copying private titles or payloads into the feed. Crew OS pulls it on list access and foreground refresh, with idempotent replay and a durable cursor. There is no scheduled background synchronization worker in this release. A pending coordination response calls for a refresh, not automatic repetition of a confirmed source action. ## Connected sales workspace (pending release) The proposed Max release adds **Sales workspace** at `/sales-workspace` as a shared starting point for audiences, intent, campaigns, workflows and pipelines. Implementation: [Max PR #868](https://github.com/Digital-Crew-Technologies/max-agent/pull/868). This section describes the implementation under review, not verified production behavior. Its products/services catalog requires a separate additive migration. The connected map shows saved relationships between lists, intent monitors and proposals, campaigns, workflows, stage rules, people/deal pipelines and attributed meeting records. Select a card to inspect its links and open the existing editor. Search and the List view make larger workspaces easier to navigate. Coverage notes identify truncated or unavailable groups; permissions still apply to every object. The map checks for changes every 15 seconds while visible. A saved link describes configuration; it does not prove that an action ran or caused a meeting. - **Audience and intent:** find people or organizations, save lists, and use ICP and intent tools to decide whom to approach and when. - **Campaigns:** configure messages, waits and follow-ups for an audience. - **Workflows:** choose a trigger, conditions and actions. The editor opens in Steps, with Canvas still available for visual editing. - **Pipeline and deals:** inspect progress and opportunities, including their products and services. Templates create editable drafts for reply handoff, qualified opportunity creation, won-deal onboarding, weekly review reminders and a LinkedIn connection/follow-up campaign. Choose the required stage, board, owner or campaign, then review the draft in its editor before publishing. ICP/ranking, intent, meeting and metrics setup remain linked to their native tools. Templates do not automatically score records, book meetings, compute review metrics or activate a sales workspace bundle. **Products & services** supports draft, active and archived offerings with optional pricing and typed custom fields (text, number, yes/no, date and choice). Add active offerings to open deals with agreed quantities, price and scope. Deal items preserve a snapshot of the offering and field definitions; future catalog edits do not change the agreed item. Item totals do not overwrite the deal amount. Removed items remain in history, and closed deals cannot be changed. This feature does not create invoices or implement quote acceptance. Field definitions are immutable in this release. If an open deal's currency changes later, existing items retain their original currency and may still be removed; new items must use the deal's current currency. The map uses the authenticated current workspace, independently of any combined list view. Workspace-wide catalog access requires full deal access, and deal-item operations also enforce record and commercial field permissions. Until the migration is applied, the catalog shows a setup-pending message. ## Documents and signatures (pending release) > Prepared with the document-signature implementation. Requires the two Max > migrations, provider setup and `DOCUMENT_SIGNATURES_ENABLED=true`; this is > not a statement that signing is enabled in production. Open **Documents** from the menu to prepare NDA, MSA, SLA, SOW, DPA, or other PDF agreements. An admin connects OpenSign, DocuSign, or Dropbox Sign (formerly HelloSign) under **Signing connections**. OpenSign requires an API-enabled cloud or paid self-hosted plan. DocuSign uses OAuth; OpenSign and Dropbox Sign use verified API keys. Test connections are clearly labeled. Start from a person, company, deal, campaign, or pipeline stage to carry that context into the document. Upload the final PDF (up to 2 MB), add recipients, place signature fields in the preview, and save a draft. Dropbox Sign appends its own signature page. Review the document and signer emails before confirming send. Clients receive the provider invitation and sign through its hosted flow. Saving does not send directly; an active workflow can send a newly saved draft. The original and signer configuration are immutable after saving; use a new draft for a changed agreement. Max follows sent, viewed, partially signed, completed, declined, expired and voided states. Refresh retrieves authenticated provider status; scheduled polling recovers missed notifications and archives the completed PDF privately. You can download the original or signed PDF. Provider-side cancellation and evidence certificates remain in the provider console. A timeout never causes Max to resend automatically: admins reconcile the existing request using its Max reference and recipients. A **Document** workflow can watch `status → completed` and move the linked person/deal to a stage, enroll or remove the person from a campaign, create tasks, or notify the team. **Request signature** sends a selected prepared draft once; it does not automatically clone contracts for a campaign audience. Deal moves preserve Max's normal Won requirements and stage rules. Workflows recheck their author's current access; turning signing off parks signature-driven runs. Document access follows Deals rights, linked-record permissions, field visibility and explicit deal/campaign blocks. Restricted workspace memberships cannot open contracts. Admins can renew a connection's credentials for the same signing account; outstanding contracts keep their original provider association. Disconnect is blocked until pending requests end and final PDFs are archived. The browser-session document API is not part of the public API-key/MCP catalog. ## Configuration The maintained variable catalogue is [`max-agent/.env.example`](https://github.com/Digital-Crew-Technologies/max-agent/blob/main/.env.example). The required platform groups are: - Max Supabase URL, anon key and service-role key; - admin/hub Supabase URL, anon key and service-role key; - Crew OS auth-hub URL and Max public URL; - Unipile API configuration; - `CRON_SECRET`. AI, MCP, data suppliers, meeting bots, OAuth integrations, billing markup, observability and feature flags are documented as optional groups in that file. Secrets are server-only unless their name intentionally begins with `NEXT_PUBLIC_`. ## Running the repository ```bash git clone https://github.com/Digital-Crew-Technologies/max-agent.git cd max-agent npm ci cp .env.example .env.local npm run dev ``` Max runs on `http://localhost:3006`. The main verification commands are: ```bash npm run lint npm run typecheck npm test npm run build ``` CI uses Node.js 22 and supplies non-secret placeholder values for required configuration during the production build. ## Deployment notes - The web application deploys to Vercel. - `vercel.json` is the canonical scheduled-route inventory. - Supabase migrations are deployed and verified separately from application code. - Vexa is shared/self-hosted infrastructure. Moving between Vexa deployments requires the provider-identity cutover runbook in the Max repository; token ids must never be reused across Vexa databases. - Deepgram speech configuration belongs with the shared speech bridge beside Vexa, not in the Max application environment. ## Documentation maintenance When shipped behaviour changes: 1. update the Max repository README or focused runbook if maintainer orientation changes; 2. update this guide for human- or agent-visible behaviour; 3. update the curated OpenAPI file for public API contract changes; 4. refresh the verification commit/date; 5. run the docs-site build so human pages, `/content/`, `llms.txt` and `llms-full.txt` stay aligned. ### Pipeline saved filtered views (pending release) In **Pipeline → Board**, choose a pipeline and narrow it by list, search or stage. Click **Save view**, name it, and choose **Everyone in the workspace** or **Only me**. Shared views are available to full workspace members with Pipeline and People access; private views are visible only to their creator. Select a saved tab to restore its filters. **Save** updates your saved view, **Reset** discards unsaved changes, and **Save view** creates another version. The creator or a workspace admin can change a shared view; other members can duplicate it. **Copy view link** lets a colleague reopen the saved filters in the same workspace. Unsaved changes are not included in that link. Saved views persist across sessions. Sharing a view does not grant access to its contacts or lists. Board views are separate from Table's People views and from Canvas; workspace-wide default Board views are not available yet. --- # CLAIRE (claire-agent + claire-mcp-server) Claire is Digital Crew's **AI market-research agent**. Given a URL or an entity name, she browses intelligently and returns structured research: company/person profiles, competitor landscapes, market-watch diffs, and sourced answers. Claire originally powered Max's client onboarding (URL → company profile) and has grown into a Research Hub consumed by every agent in the crew. Claire is two codebases: | Component | Repository | What it does | |---|---|---| | **claire-agent** | [`Digital-Crew-Technologies/claire-agent`](https://github.com/Digital-Crew-Technologies/claire-agent) | Monorepo: Next.js frontend (`claire.digitalcrew.tech`), FastAPI backend (`claire-api.digitalcrew.tech`), arq worker, and the agent SDKs | | **claire-mcp-server** | [`Digital-Crew-Technologies/claire-mcp-server`](https://github.com/Digital-Crew-Technologies/claire-mcp-server) | Stateless MCP adapter (`claire-mcp.digitalcrew.tech`) that forwards tool calls to the Research Hub | ``` MCP client ──▶ claire-mcp-server ──Bearer passthrough──▶ claire-api (hub) ──▶ Supabase Agent SDKs (Python/TS) ────────────────────────────────▶ claire-api (hub) Crew OS / max-agent ──X-Service-Secret──▶ claire-api & claire-mcp /svc ``` ## claire-agent ### Architecture | Layer | Detail | |---|---| | Frontend | Next.js 15 (App Router) at https://claire.digitalcrew.tech — largely a proxy layer of `app/api/*` route handlers over the backend | | Backend | FastAPI (Python 3.12), "Claire API", at https://claire-api.digitalcrew.tech | | Database | Supabase (PostgreSQL); migrations in `backend/migrations/` are applied manually in the SQL editor | | Queue | Redis 7 + **arq** worker container; when Redis is absent (or `QUEUE_ENABLED=false`) hub jobs fall back to **inline** execution via FastAPI `BackgroundTasks` | | Deployment | Hostinger VPS at `/opt/claire/`, Docker Compose (`frontend`, `backend`, `redis`, `worker`) behind **Traefik** (Let's Encrypt TLS, label-based routing, no exposed ports) | `NEXT_PUBLIC_*` env vars are baked into the frontend image at build time (Docker ARG/ENV) — they are not available at runtime. CORS allows `localhost:3000` plus `https://(*.)digitalcrew.tech`. Routers mounted in `backend/app/main.py`: `/research`, `/deep-research`, `/hub/v1`, `/hub/v1/market-watch`, `/orgs/v1`, `/account/v1`, `/pdf`, and `GET /health`. ### The scraping engine The config-driven engine (`backend/app/services/scraper/`) runs a **fetcher waterfall** followed by an extractor: - **Fetcher tiers:** `http` (fast HTTP) → `camoufox` / `scrapling_camoufox` (stealth browser) → `playwright` → `crawl4ai` → `browser_use` (Cloud AI browsing — strictly last resort, only when the profile allows it). Browser tiers share a semaphore (`max_browsers`); results record `tier_used` / `extractor_used` / `chars` for telemetry. - **Extractors:** `trafilatura` (default), `crawl4ai_markdown`, `readability`. - **Job profiles:** each hub request type has a `*_LITE` profile (small chain, no browser-use — CI/dev) and a `*_FULL` profile (full waterfall). `mode` per request overrides `HUB_DEFAULT_MODE` (default `full`). The legacy `POST /research` endpoint (`{url, hint?}` → `{job_id}`) uses the original three-tier Scrapling pipeline + OpenRouter `gpt-4o-mini` extraction + browser-use fallback, writing `what_you_do`, `who_you_sell_to`, `proof_point` to `research_jobs`. ### The Research Hub (`/hub/v1/*`) The agent-facing surface. Every submit endpoint runs the same **5-step pipeline**, strictly in order: 1. **Auth** — `require_hub_user` (see below) 2. **Normalize** — the Phase-5 security gate (see below); denials return `400 {error:"request_denied", reason_code}` and never log the payload 3. **Profile** — resolve the scraper/job profile from request type + mode 4. **Persist** — insert a `pending` row into `hub_jobs`; DB failure → `503` and no enqueue 5. **Enqueue** — arq via Redis, or inline `BackgroundTasks` dispatch when the queue is unavailable. Response: `{job_id, status:"pending", type, agent, dispatch:"queued"|"inline"}` | Method | Path | Worker | Purpose | |---|---|---|---| | POST | `/hub/v1/search` | `run_search` | Quick web search + short sourced answer | | POST | `/hub/v1/deep-research` | `run_deep_research` | Long-form person/company profiling | | POST | `/hub/v1/market-watch` | `scrape_competitor` | Scrape + diff a competitor URL against `criteria` | | POST | `/hub/v1/competitor-finder` | `find_competitors` | Seed URL → competitor landscape | | GET | `/hub/v1/jobs/{job_id}` | (read) | Poll status/result | Market-watch also has CRUD at `/hub/v1/market-watch/*` (watches, changes, notify, scrapes), all email-scoped, plus an **hourly cron** in the worker that re-scrapes due watches by frequency (daily/weekly/monthly/quarterly). ### Organizations scraping (`/orgs/v1/*`) Turns a company URL into structured organization data, one section at a time: **brand** (name, tagline, description, logo, industry, HQ, founded year, social links, contact), **products & services** (offerings with categories, target customers, key features), and **pricing** (model, currency, free tier, plans with features). Runs the same 5-step pipeline as the hub and lands jobs in `hub_jobs` with `request_type='org-scrape'` (worker: `run_org_scrape`). | Method | Path | Purpose | |---|---|---| | POST | `/orgs/v1/scrape` | `{agent, url, kinds?, mode?}` — any combination of sections; `kinds` defaults to all three | | POST | `/orgs/v1/brand` | Brand fetching (sugar for `kinds=["brand"]`) | | POST | `/orgs/v1/products-services` | Products & services fetching | | POST | `/orgs/v1/pricing` | Pricing fetching | | GET | `/orgs/v1/jobs/{job_id}` | Poll status/result (owner-scoped like hub jobs; a service caller without `X-On-Behalf-Of` may read the ownerless jobs it created) | | GET | `/orgs/v1/profiles?url=` | Latest saved profile for a domain (shared research — any authenticated caller) | **Content fallback chain** (`services/org_sources.py`): the Scrapling engine reads the site first; when a page can't be read (or comes back under `ORG_SOURCE_MIN_CHARS`, default 200) the worker falls through hosted providers — **Tavily** (extract API) → **Exa** (contents API) → **Perplexity** (`sonar`, generative last resort). Order is overridable via `ORG_SCRAPER_SOURCES`; a provider whose API key is unset is skipped silently, so the chain degrades gracefully. Paid calls are tallied into the shared token ledger (`tavily`/`exa`/`perplexity` per-call USD knobs) and billed as `claire_org_scrape` after completion. Per section, well-known sub-pages (`/about`, `/products`, `/services`, `/pricing`, `/plans`) are probed with Scrapling only — the paid chain runs at most once per section, on its primary URL. Extraction is one `gpt-4o-mini` call per section with a strict "null when absent, never invent" contract. **Persistence:** every job's full envelope (`{url, domain, kinds, sections, sources, unavailable}`) lives in `hub_jobs.result`; the latest sections are additionally **merged per-domain** into `org_profiles` (migration `007`), so agents can read the current profile without re-scraping. A pricing-only scrape never blanks out a previously captured brand section. **Frontend:** the `/organizations` page (sidebar → Organizations) lets a human enter a URL, toggle the sections, watch the job run, and browse the result cards — or load the last saved profile for a domain instantly. The browser talks to `app/api/organizations` which proxies to the backend with `X-Service-Secret`. **Workers are idempotent** — each re-reads its `hub_jobs` row and short-circuits if the job is already `running`/`complete`, so an arq retry or a duplicate enqueue can't double-execute. **Engine gating (deep research)** — `services/research/gate.py` selects the orchestrator: per-request `context.engine` (`langgraph` | `swarms`) → env `RESEARCH_ENGINE` → default rule (**person → LangGraph, company → swarms**). The swarms import is probed; any ImportError falls back to LangGraph, so a bad dependency bump can't take production down. Both engines return the same `{profile, confidence, summary, iterations}` shape: - **LangGraph engine** — the production-proven multi-round pipeline: generate queries → Brave searches → prioritize URLs → scrape batch → evaluate coverage → loop (max 4 rounds, confidence threshold 8.0) → synthesize with `gpt-4o`. - **Swarms engine** — multi-agent flow (company: Business/Funding/Market/News/Social; person: Profile/News/Social/Credibility) synthesized by `gpt-4o-mini`, scraping only through the shared engine. ### Auth `require_hub_user` resolves the caller in this order: 1. **`X-Service-Secret`** header (checked first, constant-time against `SERVICE_AUTH_SECRET`) — trusted services (Crew OS, max-agent); the job is attributed to the `X-On-Behalf-Of` email. A present-but-wrong secret is a hard 401. 2. If `REQUIRE_AUTH != "true"`, auth is disabled (dev-safe sentinel). 3. **`Authorization: Bearer`** — either a **personal access token** (`claire_live_...`) or an **auth-hub session JWT** validated against the shared hub Supabase project. **Personal access tokens** (`account_tokens` table): shape `claire_live_<32 chars>` mirroring Max's `max_live_*`. Only the argon2 hash is stored; plaintext is shown once. Lookup is O(1) by the first 16 characters (`prefix`). Managed at `/account/v1/tokens` (issue/list/revoke), which requires a *session* — a leaked PAT cannot mint more PATs. **Job read scoping:** `GET /hub/v1/jobs/{id}` only returns jobs whose `owner_email` matches the caller; mismatches return **404** (never 403) so job existence is not confirmed. ### The normalizer (security gate) `services/normalizer.py` is a fail-**closed** kill gate run on every hub payload: - **Layer 1 (always on, pure Python):** shape limits; SSRF guard (scheme allowlist, no credentials in URLs, DNS-resolved private/loopback/link-local/ULA and cloud-metadata IP blocklist, IPv4-mapped-IPv6 unwrap); prompt-injection regexes (OWASP LLM01); ToS blocklist; detect-secrets scan; PII redaction (email/SSN/credit-card-with-Luhn/phone/IPv4 → `[REDACTED:*]`, deny at >5 hits). - **Layer 2 (optional, `NORMALIZER_LLM_CHECK=true`):** a `gpt-4o-mini` classifier, also fail-closed. Denials log only a `reason_code`, never the payload. ### Database | Table | Purpose | |---|---| | `research_jobs` | Legacy `/research` jobs (`what_you_do`, `who_you_sell_to`, `proof_point`) | | `deep_research_jobs` | Legacy `/deep-research` jobs | | `hub_jobs` | Research Hub + org-scrape jobs: `job_id`, `request_type` (search/deep-research/market-watch/competitor-finder/org-scrape), `agent`, `owner_email`, `status` (pending/running/complete/failed), `payload`/`result` jsonb, auto-touched `updated_at` | | `org_profiles` | Latest merged org profile per `domain`: `brand`/`products_services`/`pricing` jsonb + per-section `sources` provenance, `last_job_id` (RLS on; service-role access only) | | `account_tokens` | PATs: `prefix` PK, `user_email`, argon2 `token_hash`, `revoked_at` | | `competitors` / `changes` / `scrapes` | Market-watch domain (scrapes are diffable by URL for the hub path) | | `competitor_finder_searches` | Competitor-finder results | Billing tables (`token_management`, `token_movements`, `profiles`) live in the **shared admin/hub Supabase project** (see Crew OS): Claire debits the crew-wide token pot after deep-research and competitor-finder jobs complete — best-effort, idempotent on `job_id`, floored at 0, with paid-API costs (Brave, etc.) converted via markup knobs. ### SDKs Both SDKs live in `sdks/`, mirror each other 1:1, and default to `https://claire-api.digitalcrew.tech`. Each request type has `submit_*` (returns the envelope immediately) and `run_*` (submit + poll: 2s interval, 600s budget). Errors: `HubError`, `HubJobFailedError` (with error code), `HubJobTimeoutError`. **Python — `claire-hub`** (used by Hermes): ```python from claire_hub import ClaireHubClient async with ClaireHubClient(agent="hermes", api_token=token) as hub: job = await hub.run_deep_research("Acme GmbH", entity_type="company") ``` **TypeScript — `@digital-crew/claire-hub`** (used by Max and Sophie; zero runtime deps, Node 18+/edge): ```typescript import { ClaireHubClient } from '@digital-crew/claire-hub'; const hub = new ClaireHubClient({ agent: 'max', apiToken }); const job = await hub.runDeepResearch({ name: 'Acme GmbH', entity_type: 'company' }); ``` Both accept `trace_id` / `traceId` and emit it as an **`X-Trace-Id`** header so caller Langfuse/OTel spans correlate with hub traces. Org scraping is exposed the same way: `submit_org_scrape` / `run_org_scrape(url, kinds=["brand","pricing"])` in Python, `submitOrgScrape` / `runOrgScrape({ url, kinds })` in TypeScript. Org jobs poll through the same `get_job` flow. ### Observability `services/observability.py` is a **fail-open Langfuse shim** (SDK v3, OpenTelemetry-based). Enabled only when `LANGFUSE_PUBLIC_KEY` + `LANGFUSE_SECRET_KEY` are set; `LANGFUSE_TRACING=false` is an explicit kill switch; any init failure degrades to no-ops. Worker conventions: - One `trace()` per worker invocation, keyed `session_id=job_id`, `user_id=owner_email`, tagged `[agent, request_type, mode]` - Every LLM call wrapped in a `generation()` with **redacted input** (e.g. `{"query_chars": N}` — never raw prompts or scrape text) and `usage_details` so token costs land in Langfuse automatically ### Environment variables (names only) Single `.env` at the repo root for both services. - **Supabase:** `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` (must be a full JWT, not `sb_secret_...`), `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY` - **LLM/search/browser:** `OPENROUTER_API_KEY`, `BRAVE_API_KEY`, `BROWSER_USE_API_KEY` - **Org-scrape fallbacks (all optional):** `TAVILY_API_KEY`, `EXA_API_KEY`, `PERPLEXITY_API_KEY` (+ `PERPLEXITY_MODEL`), `ORG_SCRAPER_SOURCES`, `ORG_SOURCE_MIN_CHARS`, cost knobs `TAVILY_USD_PER_CALL` / `PERPLEXITY_USD_PER_CALL` - **Frontend:** `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_AUTH_HUB_URL`, `ALLOWED_ORIGINS` - **Hub auth:** `REQUIRE_AUTH`, `HUB_SUPABASE_URL`, `HUB_SUPABASE_SERVICE_ROLE_KEY`, `AUTH_HUB_URL`, `SERVICE_AUTH_SECRET` - **Billing ledger:** `ADMIN_SUPABASE_URL`, `ADMIN_SUPABASE_SERVICE_ROLE_KEY`, plus cost knobs (`PAID_SOURCE_MARKUP`, `PAID_TOKENS_PER_USD`, `*_USD_PER_CALL`, `DEEP_RESEARCH_{LITE,FULL}_TOKENS`) - **Hub/queue/engine:** `HUB_DEFAULT_MODE`, `NORMALIZER_LLM_CHECK`, `REDIS_URL`, `QUEUE_ENABLED`, `WORKER_MAX_JOBS`, `JOB_TIMEOUT`, `RESEARCH_ENGINE` - **Langfuse:** `LANGFUSE_PUBLIC_KEY`, `LANGFUSE_SECRET_KEY`, `LANGFUSE_HOST`, `LANGFUSE_TRACING` ### Deploying ```bash # On the VPS cd /opt/claire && git pull && docker compose up -d --build backend # backend cd /opt/claire && git pull && docker compose up -d --build frontend # frontend (rebakes NEXT_PUBLIC_*) docker compose up -d backend # reload .env changes (not `restart`) ``` ## claire-mcp-server A **stateless protocol adapter**: MCP client → claire-mcp-server → claire-api. No database, no queue, no tracing — every hub tool call is forwarded to `https://claire-api.digitalcrew.tech/hub/v1/*` with the caller's own bearer token. Two capabilities additionally run **inline in-process** (competitor finder and company profile — full local pipelines that scrape and call external APIs directly). ### Stack and transport Python 3.12, **FastMCP** (`FastMCP("claire")`), Starlette + Uvicorn, httpx. MCP **Streamable HTTP** at `/mcp`. Health at `GET /health` and `GET /healthz/live` (checks upstream claire-api). ### Tools 13 tools registered: | Tool | Kind | What it does | |---|---|---| | `submit_search(query, mode)` | hub proxy | POST `/hub/v1/search` → `{job_id, status}` | | `submit_deep_research(name, entity_type, context?, mode)` | hub proxy | POST `/hub/v1/deep-research` | | `submit_market_watch(url, criteria?, mode)` | hub proxy | POST `/hub/v1/market-watch` | | `get_hub_job(job_id)` | hub proxy | Poll any submitted job until `complete`/`failed` | | `submit_competitor_finder(url, …)` | **inline** | Full local pipeline: identify the business, sweep ~20 sources (Brave, DuckDuckGo, GitHub, Product Hunt, Trustpilot, YouTube, …), dedupe by domain, LLM-judge relevance → scored competitor list, synchronous | | `submit_company_profile(url, mode)` | **inline** | 7-section profile (products/positioning, website, ICP, brand, SEO, competition, social). Never raises — failed sections degrade to `{"status":"unavailable"}`. `lite` mode skips paid enrichments | | `add_market_watch`, `list_market_watches`, `get_market_watch`, `delete_market_watch`, `list_market_watch_changes`, `mark_changes_notified`, `list_market_watch_scrapes` | hub proxy | Market-watch CRUD against `/hub/v1/market-watch/*` | All hub payloads are submitted with `agent: "mcp"`. Hub proxies return `{job_id}` for polling; inline tools return full results synchronously. ### Auth - **User path:** `BearerMiddleware` captures the caller's `Authorization: Bearer claire_live_...` header per request and re-attaches it on the upstream call — the MCP server never validates or stores tokens. Missing token → a descriptive error pointing to the token page. - **Service path (`/svc`):** trusted internal callers (Crew OS onboarding, Max) use `X-Service-Secret` matched against `SERVICE_AUTH_SECRET` (constant-time; unset secret disables the surface with 503). `POST /svc/company-profile` returns `202 {job_id}`, runs as an in-memory asyncio task (4-minute hard timeout, 15-minute result TTL), polled via `GET /svc/company-profile/{job_id}`. ### Getting a token 1. Sign in at https://claire.digitalcrew.tech 2. **Settings → API Tokens** → create a token 3. Copy the `claire_live_...` value (shown once) ### Connecting from Claude Desktop ```json { "mcpServers": { "claire": { "url": "https://claire-mcp.digitalcrew.tech/mcp", "headers": { "Authorization": "Bearer claire_live_PASTE_YOUR_TOKEN_HERE" } } } } ``` Any Streamable-HTTP MCP client with custom-header support works the same way. Local dev: `uvicorn mcp_server.main:app --reload --port 8001`. ### Deployment Two wired targets; production `https://claire-mcp.digitalcrew.tech` is deployed via **Vercel** in the active CI pipeline (`api/index.py` + `vercel.json`, `maxDuration: 60`), with a Docker/Traefik alternative for self-hosting alongside claire-agent. The manual Deploy workflow deploys `--prod`, waits for `/health`, then runs a post-deploy smoke test (submits a real search and polls it). **Env vars (names only):** `CLAIRE_API_URL`, `SERVICE_AUTH_SECRET`; inline-pipeline keys `OPENROUTER_API_KEY`, `BRAVE_API_KEY`, `EXA_API_KEY`, `FIRECRAWL_API_KEY`, `YOUTUBE_DATA_API_KEY`, `GITHUB_TOKEN` (+ `CF_*` tuning knobs); optional company-profile enrichment keys (`GETLEADS_*`, `SCRAPECREATORS_*`, `DATAFORSEO_*`, `BRANDFETCH_*`). > **Cost note:** company-profile `mode="full"` calls paid APIs with no billing hook yet — keep it behind internal-only usage. ## Who calls Claire | Caller | Path | Auth | |---|---|---| | **Max** | TS SDK (`agent:"max"`) and max-agent's `/api/v1/claire/*` proxy | PAT / `X-Service-Secret` + `X-On-Behalf-Of` | | **Sophie** | TS SDK (`agent:"sophie"`) | PAT | | **Hermes** | Python SDK (`agent:"hermes"`) | PAT | | **Crew OS** | claire-mcp `/svc/company-profile` (onboarding) and claire-api `/pdf` (report rendering) | `X-Service-Secret` | | **MCP clients** (Claude, Cursor, …) | claire-mcp-server `/mcp` | Bearer PAT passthrough | ## Known drift to reconcile - claire-mcp-server's README documents 5 tools; the server registers **13** (company profile + 7 market-watch CRUD tools are undocumented there). - claire-agent's docs reference migrations `001`/`002`, but the current tree contains only `003`–`007`. - The `/orgs/v1/*` organizations-scraping surface documented above is **absent from `public/openapi/claire-agent-v1.yaml`** in this repo — the spec needs those paths added from the claire-agent code. --- # SOPHIE (sophie-agent) Sophie is Digital Crew's **AI HR & recruitment agent**: job offers, candidate pipelines, AI resume screening and AI interview screening. Product site: https://sophie.digitalcrew.tech. | Component | Repository | What it does | |---|---|---| | **sophie-agent** | `Digital-Crew-Technologies/sophie-agent` | The Sophie product and its REST API at `https://sophie.digitalcrew.tech/api/v1/*` — job offers, candidates, screenings, interviews, and API-key management | Sophie is a Next.js 15 (App Router) app with its own Supabase instance. Authentication is delegated to the Digital Crew **Auth Hub** (shared SSO from Crew OS); Hub-owned entities such as job-offer creation and interview generation flow through the Hub, while Sophie's own Supabase holds applications, screenings and LinkedIn posting state. ## The sophie-agent API Full reference: the OpenAPI 3.0 spec in this repo ([`public/openapi/sophie-agent-v1.yaml`](../public/openapi/sophie-agent-v1.yaml)), rendered by the docs site (Scalar). Highlights: - **Server:** `https://sophie.digitalcrew.tech` (production). - **Auth:** `Authorization: Bearer `. Two token shapes: - **API key** — opaque, prefixed `sophie_live_`, with a fixed scope set (`job-offers:*`, `candidates:*`, `screenings:*`, `interviews:*`). Missing scope → `403` with `requiredScope`. Keys are SHA-256-hashed at rest and shown once at creation. - **Hub session token** — used by the web app; session callers bypass scope checks. API-key **management** (`/api/v1/api-keys`) is session-only: a key can never mint or revoke keys. - **Envelopes:** single/list `{data}`, state transitions `{success, ...}`, error `{error}` (the 403 body adds `requiredScope`). - **Surface:** 5 tags, 16 operations — API keys (create / list / revoke), job offers (list, create, read, update, publish/unpublish — scoped to the caller's own offers), candidates (list per offer with screening summaries, add, read in full with interview feedback, move across pipeline stages), screenings (read results, launch/retry a screening — resume-based only for API-key callers), and interviews (list the caller's AI interviews, create one with AI-generated or supplied questions). - **Async screening:** `POST /api/v1/candidates/{id}/screening` returns `202` and runs OCR + AI evaluation in the background; poll `GET /api/v1/screenings?candidate_id=…` until `completed` or `failed`. - **Interview creation:** `POST /api/v1/interviews` generates the question list with AI (same pipeline as the dashboard) unless `questionList` is supplied, and returns the candidate-facing `interview_url` (public page + access token). ## API keys Keys are managed in the dashboard at `https://sophie.digitalcrew.tech/api-keys` (Developer → API Keys) or via the session-only `/api/v1/api-keys` endpoints. Each key carries a name and an explicit scope selection; revocation is a hard delete, so a revoked key stops validating immediately. Only the SHA-256 hash is stored — the `sophie_live_…` secret appears exactly once, in the create response. --- # KATE (kate-agent) Kate is Digital Crew's **AI SEO intelligence agent**: keyword research, rank tracking, backlink analytics, site audits and SERP intelligence. Product site: https://kate.digitalcrew.tech. Kate is a single codebase: | Component | Repository | What it does | |---|---|---| | **kate-agent** | `Digital-Crew-Technologies/kate-agent` | The Kate product and its REST API at `https://kate.digitalcrew.tech/api/v1/*` — a fast, opinionated dashboard over the full [DataForSEO API v3](https://docs.dataforseo.com/v3/) surface | Kate is the SEO sibling of **Max** (sales) and **Claire** (research): same identity hub, same token ledger, same Supabase-per-agent architecture. Instead of reselling SEO seats, Kate orchestrates DataForSEO's pay-as-you-go data feeds on the platform's own account and bills users in Digital Crew tokens — so teams pay only for the data they pull. ## Stack - **Next.js 16 (App Router)**, React 19, TypeScript, Tailwind v4, shadcn/ui, Recharts, Zod. - **Two Supabase instances**: Kate's own project (projects, search history, rank tracker, site audits, API keys) and the shared **Hub** project (`api.digitalcrew.tech` — auth, workspaces, token ledger), reached with the admin service client. - **DataForSEO** for every data feed. All calls run server-side on the platform account; credentials never reach the browser. - Deployed on **Vercel** (`output: standalone`, framework `nextjs`). Local dev on port `3007`. - Feature-sliced layout: each tool is a vertical under `src/features//` (`schemas` / `handlers` / `services` / `ui`) with a DataForSEO client in `src/lib/services/dataforseo/` and a thin route in `src/app/api/v1/`. ## Identity and auth Follows the platform conventions (see [ecosystem overview](./README.md)): - **SSO from Crew OS.** Users log in once against the Hub Supabase (`https://www.digitalcrew.tech`) and are handed to Kate via the auth-hub redirect. Kate validates the same session JWTs against the admin (hub) Supabase project. - **Personal access tokens**, same shape as the other agents: hub-provisioned keys prefixed **`kate_live_`**, accepted as `Authorization: Bearer ` and verified locally against Kate's `api_keys` table (no hub round-trip). Session JWTs are also accepted. - Every data query is **scoped by workspace** when the user belongs to one (from `user_metadata.workspace_id`), otherwise by user id. - Scheduled rank checks are gated by `CRON_SECRET` (`Authorization: Bearer `). ## Billing A single token ledger funds the whole crew. Kate never shows the raw DataForSEO USD cost; each call is converted to hub tokens and debited from the user's balance in the Hub Supabase (`token_management` with a `>= 0` check, `token_movements` as a signed audit log with idempotency keys — the same tables Max and Claire debit). ``` tokens = ceil(cost_usd × DATAFORSEO_TOKEN_MARKUP ÷ TOKEN_PRICE_IN_USD) ``` `TOKEN_PRICE_IN_USD` stays aligned with the orchestrator's checkout rate; `DATAFORSEO_TOKEN_MARKUP` defaults to 1.5; the ledger movement type is `seo_research`. Every research handler runs a **pre-flight gate** (`requireTokens` → `402` with `code: insufficient_tokens` when the balance is empty) and settles the exact cost **after** a successful call (fails open on hub outages — research already succeeded). ## The kate-agent API All endpoints live under `/api/v1` and require `Authorization: Bearer `. Responses follow `{ data, count?, tokens_charged? }`; errors follow `{ error, code? }`. ### Research & analytics | Endpoint | Method | Description | |---|---|---| | `/api/v1/keywords/research` | POST | Keyword ideas / suggestions / related for a seed keyword (Labs) | | `/api/v1/keywords/overview` | POST | Full metrics for an explicit keyword list | | `/api/v1/keywords/site` | POST | Website keyword wheel — keywords a site is relevant for, clustered into topics | | `/api/v1/keywords/site/topic` | POST | Regenerate one topic cluster of a site keyword analysis | | `/api/v1/domain/overview` | POST | Rank overview + monthly visibility history | | `/api/v1/domain/ranked-keywords` | POST | Keywords a domain ranks for | | `/api/v1/domain/competitors` | POST | Organic competitors | | `/api/v1/domain/keyword-gap` | POST | Domain intersection / gap analysis | | `/api/v1/domain/top-pages` | POST | Top pages by organic traffic | | `/api/v1/domain/technologies` | POST | Technology-stack detection | | `/api/v1/backlinks/summary` · `/list` · `/referring-domains` · `/anchors` · `/timeseries` | POST | Backlink profile analytics | | `/api/v1/serp/live` | POST | Live Google SERP (advanced) | ### Market & advertising data | Endpoint | Method | DataForSEO API | Description | |---|---|---|---| | `/api/v1/google-ads/search-volume` | POST | Keywords Data → Google Ads | Search volume / CPC / competition for a keyword list | | `/api/v1/google-ads/keywords-for-site` | POST | Keywords Data → Google Ads | Keywords a website is relevant for | | `/api/v1/content-analysis/summary` | POST | Content Analysis | Brand / phrase mention volume, sentiment & top domains | | `/api/v1/content-analysis/search` | POST | Content Analysis | Individual web mentions (citations) for a phrase | | `/api/v1/social-media/engagement` | POST | Social Media | Pinterest pins + Facebook & Reddit engagement by URL | | `/api/v1/business-data/listings` | POST | Business Data | Local business listings search | | `/api/v1/business-data/reviews` | POST | Reviews | Google reviews for a business (task-based) | | `/api/v1/merchant/amazon` | POST | Merchant | Amazon product search — price, rating, seller (task-based) | | `/api/v1/merchant/google-shopping` | POST | Merchant | Google Shopping product search (task-based) | | `/api/v1/app-data/google-play` | POST | App Data | Google Play app search — rating & pricing (task-based) | | `/api/v1/app-data/app-store` | POST | App Data | Apple App Store app search (task-based) | ### Projects, tracking & account | Endpoint | Method | Description | |---|---|---| | `/api/v1/projects` · `/projects/:id` | GET/POST/PATCH/DELETE | Manage SEO projects | | `/api/v1/rank-tracker/keywords` · `/:id` · `/:id/history` | GET/POST/DELETE | Tracked keywords & position history | | `/api/v1/rank-tracker/check` | POST | Run live position checks (user or `CRON_SECRET`) | | `/api/v1/site-audit/audits` · `/:id` · `/:id/pages` | GET/POST/DELETE | On-Page crawls (task-based, auto-refreshing) | | `/api/v1/searches` · `/searches/:id` | GET | Saved search history & individual search snapshots | | `/api/v1/account/usage` | GET | DataForSEO balance & subscription status | | `/api/v1/api-keys/provision` | POST | Mint a `kate_live_*` API key for a user (service-to-service only: `X-Service-Secret` + `X-On-Behalf-Of`) | | `/api/health` | GET | Health check (no auth) | ### Brainstorm board (idea capture & digest) The idea-capture surface feeding the studio and the calendar: a free-form canvas of sticky-note and media cards (photos, audio memos, video clips — stored in the `content-media` bucket), which Kate **digests** into themes and concrete content suggestions. The same APIs back idea creation from any surface (board UI, calendar, studio intake, or external clients via PAT), and cards **promote** into a calendar entry or a Content studio piece. Full details in the kate-agent repo (`docs/IDEA_BOARD.md`). | Endpoint | Method | Description | |---|---|---| | `/api/v1/ideas` | GET/POST | List board cards / create a note or media card (`file_base64` + `file_mime` for media) | | `/api/v1/ideas/:id` | GET/PATCH/DELETE | Read, edit (incl. board position/color/status) or delete a card | | `/api/v1/ideas/positions` | PATCH | Batch position save after drag interactions (≤ 200 cards) | | `/api/v1/ideas/digest` | GET/POST | List digest runs / run Kate's digest over selected cards or the active board (token-gated, multimodal — photo cards are read) | | `/api/v1/ideas/:id/promote` | POST | Promote a card: `target: calendar` (free) or `target: studio` (ingest + analyze, billed) | ### Content studio & calendar The Content Creation Studio (drop a source → analyze → enrich → generate per-channel variants → produce media → publish) and the scheduled content calendar. Full request/response details live in the kate-agent repo (`docs/CONTENT_CREATION.md`, `docs/CONTENT_CALENDAR.md`). | Endpoint | Method | Description | |---|---|---| | `/api/v1/content-creation/pieces` | GET/POST | List pieces / drop + analyze a source (URL, text, idea, screenshot, PDF) | | `/api/v1/content-creation/pieces/:id` | GET/DELETE | Piece with its variants / delete (cascades) | | `/api/v1/content-creation/pieces/:id/enrich` | POST | Claire deep research and/or DataForSEO SEO keywords | | `/api/v1/content-creation/pieces/:id/generate` | POST | Generate selected formats (one variant each) | | `/api/v1/content-creation/variants/:id` | PATCH | Edit a generated variant | | `/api/v1/content-creation/variants/:id/regenerate` | POST | Rewrite a variant in place | | `/api/v1/content-creation/variants/:id/audio` | POST | Voice a podcast script (Deepgram TTS) | | `/api/v1/content-creation/variants/:id/assets` · `/:assetId` · `/:assetId/edit` | POST/PATCH/DELETE | Generate, attach/upload, AI-edit or remove multimedia assets (image, carousel, graph, survey, video) | | `/api/v1/content-creation/variants/:id/reel` · `/scenes/:sceneId` · `/voiceover` · `/music` · `/export` | POST/PATCH | Reel Studio — storyboard, per-scene AI clips, narration, music bed, MP4 export | | `/api/v1/content-creation/variants/:id/publish` | POST | Publish via Postiz (social) or Ghost (blog/newsletter) | | `/api/v1/content-creation/channels` | GET | Connected Postiz channels + Ghost / Deepgram / Muapi availability | | `/api/v1/content-calendar/entries` | GET/POST | Entries scheduled in a date range (or `backlog=true`) / create an entry | | `/api/v1/content-calendar/entries/:id` | PATCH/DELETE | Update or unschedule an entry / delete it | Most calls are **live** (submit → result in one request). The Merchant, App Data, Reviews and Site-Audit endpoints are **task-based**: Kate submits a DataForSEO `task_post` and polls `task_get` server-side (`runDataForSeoTask`, `maxDuration = 90`) so callers still get a single synchronous response. ## DataForSEO coverage Kate wraps the DataForSEO v3 surface behind a no-jargon UI — every tool is a form plus a results table, usable without knowing DataForSEO's query syntax: | DataForSEO API | Kate tool(s) | |---|---| | SERP | SERP Checker | | DataForSEO Labs | Keyword Research, Keyword Gap | | Domain Analytics | Domain Overview, Competitors, Ranked Keywords, Top Pages, Technologies | | Backlinks | Backlink Analytics | | On-Page | Site Audit | | Keywords Data → Google Ads | Google Ads Keywords | | Content Analysis | Brand Monitoring | | Social Media | Social Engagement | | Business Data + Reviews | Local Business & Reviews | | Merchant (Amazon + Google Shopping) | Product Research | | App Data | App Store Research | ## Known drift to reconcile - The **Content studio & calendar** surface (`/api/v1/content-creation/*`, `/api/v1/content-calendar/*`) and the keyword-wheel endpoints (`/api/v1/keywords/site`, `/api/v1/keywords/site/topic`) documented above are **absent from `public/openapi/kate-agent-v1.yaml`** in this repo — the spec needs those paths added from the kate-agent code. (The Brainstorm board surface, `/api/v1/ideas/*`, **is** in the spec.) --- # Crew OS (digitalcrew-orchestrator) Crew OS is the orchestrator of the Digital Crew platform: a single Next.js application that serves both the **public marketing site** and the **protected admin dashboard** at `digitalcrew.tech`. It owns customer identity, workspaces, billing and the shared token ledger, and routes users into each agent's dedicated app via SSO. - **Repository:** `Digital-Crew-Technologies/digitalcrew-orchestrator` - **Production:** https://www.digitalcrew.tech (Vercel) - **This repo is the hub, not the agents.** Sophie, Claire, and Max run as separate deployed services that Crew OS links out to. > Coordinated release guide prepared on 2026-09-08 against the Crew OS and Max > implementation branches. These changes require the paired migrations and > rollout; they are not a claim that production already runs this revision. ## Shared workspace experience The shell uses Max's shared primitives: grouped and nested navigation, a command menu, workspace switcher, personal appearance settings and a menu editor with labels, icons, emoji and visibility controls. Menu and task-view preferences are isolated by user and workspace; the appearance theme and sidebar collapse state apply to the browser. Unknown or unsafe routes are rejected; hiding an item is not an access-control mechanism. Workspace settings list active members and invitations. Owners and administrators can invite and manage permitted roles; the owner cannot be removed or demoted through these controls. Invitation links expire, are accepted explicitly by the verified invited account, and can be revoked. Creating an invitation returns a link; it does not send an email automatically. A stale workspace selection is resolved in the directory before tenant content loads. Crew Builder drafts are isolated by user and workspace; older drafts without a workspace binding are not restored automatically. Crew Studio keeps deployment maintenance separate from building a crew. Deployment access requires a current workspace owner/administrator who also owns the deployment. Lists and status polling omit runtime credentials; Connect loads the selected deployment's authorized details explicitly. The integrated Crew Builder flow is Hosting → Crew → Work → Model → Launch. One Click hosting saves the selected crew and model before checkout; only a verified payment can provision that saved configuration. Returning from checkout does not itself create a server. Payment restore, creation and replacement require the same active workspace and deployment owner. Docker download remains available when paid hosting is unavailable. Chat with the Crew shows the deployed crew and human directory. Agent responses retain formatted parts, tool progress and upstream-reported runtime metadata. Stop preserves partial output; concurrent regeneration uses operation IDs and revisions so late responses cannot overwrite a newer run. Private agent sessions remain owned by their creator. Share links grant expiring, revocable read-only access; they cannot invoke the agent or write messages. ## Tasks and human review The Tasks workspace replaces the old Ongoing Missions shortcut. Crew tasks show the real run, event stream, assignee, blockers and immutable artifacts. A human can approve or reject submitted work; agents cannot approve their own work. Paused or revoked agents cannot resume by sending a heartbeat. The Max tab reads authorized tasks from Max and shows their current domain state. Opening a task verifies execution and sender access before enabling an action. Approve, reject and run use the exact source version shown in the confirmation. Crew OS keeps only a stable coordination identity and status projection; Max keeps the task payload and executor. Max artifacts and their human review are shared between both apps but do not execute or complete a Max task. Committed Max task events are pulled when the source list is viewed or refreshed. The transactional event feed survives disconnects and resumes from a durable cursor; this release does not install a background sync worker. If an action succeeds but synchronization fails, refresh the source. Do not repeat the action to repair synchronization. Missing authority or synchronization is shown as an error or pending state, never a fabricated success. ## The product Two surfaces in one app: 1. **Marketing site** — landing page, pricing, contact, careers/job offers, and a markdown blog (`content/blog/`), localized under `app/[locale]/`. 2. **Admin dashboard** (`app/admin/*`) — where customers manage their crew: `my-crew`, `hire-worker`, `worker-builder`, `digital-office`, `ongoing-missions`, `billing-and-plan`, `integrations`, `api-management`, `accounts`, `chat`, settings, and more. There is also a pre-signup onboarding funnel at `app/onboarding/` ("Build my Digital Worker") that captures leads and runs company/social-profile analysis before signup. ### The crew `lib/workerUtils.ts` defines 8 named workers; 3 are currently active (i.e. have token transaction types wired up): | Worker | Role | Status | |---|---|---| | **Sophie** | HR & Recruitment Specialist | Active — `interview_creation`, `screening_creation` | | **Claire** | Market Research Analyst | Active — `chat_creation` | | **Max** | Lead Generation Expert | Active — `prospect_search` | | Kate, André, Camille, Jeanne, Boss | Client relations, finance, content, PM, CEO | Defined, not yet active | Around the core crew: - **Marketplace** (`hire-worker`, `lib/marketplaceWorkers.ts`) — a catalog of 10 additional hireable workers from ecosystem providers with per-task pricing; currently catalog/marketing data. - **Worker Builder / Hermes** (`lib/workerBuilder/generateProject.ts`) — the self-host track. Generates a downloadable Docker Compose bundle around the open-source `hermes-agent` (MIT, PyPI) so customers can run their own worker on a VPS. Agent identity (palettes, bios, skills, office rooms) lives in `lib/brand.ts`. ## Tech stack - **Next.js 15.5 (App Router)**, React 19, TypeScript 5; dev server on port 3005 with Turbopack - **Tailwind CSS 4** + Shadcn/UI (Radix), Framer Motion, next-themes - **Supabase** for auth + data (`@supabase/supabase-js`, `@supabase/ssr`), **Stripe** for billing - **next-intl** with 5 locales: `en`, `fr`, `es`, `zh`, `de` (default `en`, locale prefix always) - Claire service for PDF rendering; `jszip` for worker-builder bundles, `jsonwebtoken` for legacy OpenWebUI SSO - Hosted on **Vercel** (serverless functions + cron) ## Architecture ### Routing and middleware `middleware.ts` applies next-intl routing to public routes only (skips `/api`, `/admin`, `/auth`, `/onboarding`), injects CORS headers (production allowlist: `digitalcrew.tech` and explicitly configured domains; development adds localhost and Vercel previews — see `lib/cors.ts`), and reads the Supabase session, redirecting unauthenticated `/admin/*` traffic to the home page. ### API layer Serverless endpoints under `app/api/`, JWT-authenticated via `Authorization: Bearer` → `supabase.auth.getUser(token)`. The canonical helper is `authenticateUserAndWorkspace()` in `lib/auth/server.ts`. Main categories: | Category | Endpoints | |---|---| | Auth / session | `auth/redirect-to-agent`, `auth/verify`, `user/session-data`, `user/token-balance` | | Billing / tokens | `create-checkout-session`, `create-customer-portal-session`, `stripe-webhook`, `token/*`, `boosts/*` | | Sophie (HR) | `interviews/*`, `screenings/*`, `job-offers/*`, `apollo-sophie/*`, `organizations-sophie/*`, `prospect-lists-sophie/*` | | Max (lead gen) | `people/*`, `prospect-lists/*`, `organizations/*`, `apollo/*`, `campaigns/*` (+ multi-channel `campaigns/v1/*`), `accounts/*`, `unipile/*`, `workflow/worker` | | Claire (research) | `market-research/*` | | Onboarding funnel | `onboarding/{lead,profile,insights,diagnose,complete,status}` | | Chat (OpenWebUI) | `openwebui-auth`, `openwebui/[...path]`, `webui-proxy` | | Marketing | `contact`, `career`, `newsletter`, `feedback/submit` | | **Crew OS Hub** (coordination) | `hub/v1/{health,overview,agents,agents/[agentId],agents/heartbeat,tasks,tasks/[taskId],tasks/[taskId]/transition,tasks/[taskId]/comments,tasks/[taskId]/blockers,tasks/claim,runs,runs/[runId],runs/[runId]/events,wakeups,wakeups/claim,costs,activity,cron}` | ### Crew OS Hub API (`/api/hub/v1`) Beyond the per-agent product endpoints, Crew OS exposes a **coordination and observability layer** — the control plane the crew runs on. It is an agent registry, a ticket system with atomic checkout and dependency blocking, execution runs with an append-only event stream, a DB-backed wakeup queue, a per-agent cost ledger, and an immutable audit feed. Data model adapted from [Paperclip](https://github.com/paperclipai/paperclip) (MIT); tables are `hub_*` (service-role only). It powers the `/admin/hub` dashboard and is the API workers call to pull work and report progress. Hub callers are **workspace agent tokens** (`Authorization: Bearer dch_…`) or **dashboard users** with a verified Hub session and active workspace membership. Agent tokens have explicit action grants, expiry and revocation; the issuing administrator's current membership is checked. Shared service secrets and agent names cannot impersonate a Hub agent. Registration and token management require an authorized human administrator. Health is public; the tenant-enumerating cron requires `CRON_SECRET`. The curated OpenAPI reference documents selected public operations. Current routes, validators and migrations take precedence over older examples. Newer code follows a **feature-sliced layout** under `features/` (`accounts`, `onboarding`, `workspace` — each with `schemas/`, `services/`, `handlers/`, `repositories/`, `ui/`), coexisting with the older `app/api` + `lib/` pattern. ### Multi-database architecture Domain services have separate Supabase stores. Current Crew OS/Max authorization uses verified user and workspace UUIDs plus explicit source grants. Legacy email-keyed domain routes still exist outside the shared messaging/task boundary; an email match is not sufficient to select or authorize a current workspace. | Instance | Env vars | Holds | |---|---|---| | **Hub** (main) | `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY` | Auth users, profiles, subscriptions, token ledger, workspaces, connected accounts, onboarding caches | | **Sophie** | `SOPHIE_SUPABASE_URL`, `SOPHIE_ANON_KEY`, `SOPHIE_SERVICE_ROLE_KEY` | Interviews, screenings, candidates, Sophie orgs/prospect lists | | **Max** | `MAX_SUPABASE_URL`, `MAX_ANON_KEY`, `MAX_SERVICE_ROLE_KEY` | Prospect searches, people, organizations, campaigns, workflow queue | | **Claire** | `CLAIRE_SUPABASE_URL`, `CLAIRE_SERVICE_ROLE_KEY` | Market-research `Chats` (keyed by `libId`) | ## Auth model Crew OS verifies browser JWTs or same-origin session cookies against Supabase. Workspace selection comes from the authenticated request and is checked against active membership before data access. Scoped resource APIs reject an explicit invalid workspace. The workspace directory can recover to another verified active membership and announces the selection change. Cookie-authenticated mutations require the exact origin. Backend service-role clients still require tenant predicates and action grants. ### SSO handoff to Max Max starts a PKCE/state challenge, then redirects to Crew OS. Crew OS stores a hashed, one-use handoff code bound to the user, live session, selected workspace, Max audience and challenge. The code expires after 60 seconds. Max exchanges it server-to-server and validates the resulting membership before committing its independent session cookies. Access and refresh tokens never appear in browser redirects. Older token-in-URL handoffs are rejected; additional agents must implement the registered audience contract before using it. ## Billing and tokens Stripe subscriptions + one-time token top-ups + customer portal; webhook at `/api/stripe-webhook`. | Tier | Price | Agents | Tokens/month | |---|---|---|---| | Starter Pack | $49/mo ($479/yr) | 5 | 100,000 | | Business Accelerator | $199/mo ($1,910/yr) | 12 | 500,000 | | Market Supremacy | Custom | Unlimited | Unlimited | **Token ledger (Hub DB):** `token_management` holds one row per user (`current_balance bigint CHECK >= 0`); `token_movements` is the signed audit log with idempotency keys. Movement types include `interview_creation`, `screening_creation`, `chat_creation` (150 tokens in code), `prospect_search`, `job_offer_creation`, `subscription_grant`, `openrouter_message_preview`, `boost_purchase`, `purchase`. This same ledger is debited by Claire's backend for hub research jobs (see [claire.md](./claire.md)), so one pot funds the whole crew. ## Key database tables **Hub:** `profiles`, `subscriptions`, `token_management`, `token_movements`, `workspaces`, `workspace_members`, `accounts` (connected channel accounts: unipile/twilio/sendgrid/mailgun × email/linkedin/outlook/sms/whatsapp), `account_rate_limits`, `onboarding_leads`, `company_profiles` (cached Claire company profiles keyed by domain, per-IP rate limited), `social_profiles` (cached ScrapeCreators results keyed by `platform:handle`). **Sophie:** `interviews`, `screenings`, `screening_candidates`. **Max:** `prospect_searches`, `people` (full-text indexed), `organizations`, plus campaign/workflow queue tables. **Claire:** `Chats` (market-research conversations). ## External integrations | Service | Where | Purpose | |---|---|---| | **claire-mcp-server** (`claire-mcp.digitalcrew.tech`) | `lib/claire-mcp.ts` | Async company-profile pipeline (`POST /svc/company-profile` → poll). Auth: `X-Service-Secret` (`CLAIRE_INTERNAL_SECRET`, must match the MCP server's `SERVICE_AUTH_SECRET`). Results cached in `company_profiles`. | | **claire-api** (`claire-api.digitalcrew.tech`) | `lib/report/pdf.ts` | PDF report generation (Vercel serverless can't run headless Chrome). Auth: `X-Service-Secret`. | | **OpenWebUI** (`chat.digitalcrew.tech`) | `app/api/openwebui-auth` | Legacy OpenWebUI chat — JWT SSO signed with `OPENWEBUI_JWT_SECRET`, proxied through the app. | | **Unipile** | `lib/services/unipile/` | Unified messaging (LinkedIn, email, SMS, WhatsApp): hosted-auth account linking + webhooks; backs `accounts` and the unified inbox. | | **Instantly** | `lib/services/instantly/` | Email outreach campaigns (campaign builder). | | **Apollo** | `app/api/apollo/*`, `apollo-sophie/*` | People search + enrichment for Max (and a Sophie variant). | | **OpenRouter** | `lib/services/openrouter/` | LLM calls: campaign ideas, message previews, reports. | | **ScrapeCreators** | `lib/scrape-creators.ts` | LinkedIn/social scraping in the onboarding funnel. | | **Stripe** | `lib/stripe.ts` | Subscriptions, top-ups, portal, webhooks. | All agent integrations are plain `fetch` calls with `AbortSignal.timeout`, authenticated by `X-Service-Secret` (internal) or per-provider API keys — the orchestrator does not consume the MCP servers' npm/PyPI SDKs. ## Unibox and connected account authority Crew OS owns provider connection intents, canonical account ownership and source grants. Its inbox API forwards verified requests to Max's current messaging implementation; both apps use the same conversation store. The older `max_legacy` store is retained as historical data, and its separate Crew OS sync cron is retired. Connecting or reconnecting requires a bound intent with an expiring random secret. Provider callbacks verify the intent or required webhook secret and resolve the account from authoritative provider data. They cannot adopt a workspace through an email match. Ambiguous legacy ownership is quarantined. Source access is explicit: campaign reading, direct-message reading and sending are separate rights. Owners and administrators manage source grants for verified humans or scoped agents, with optional expiry and revocation. Management rights do not imply message access; per-conversation and private-message restrictions remain in force. Source filters apply before pagination, counts and previews. The shared composer supports email, LinkedIn and WhatsApp, emoji, and image-only messages. Up to five PNG, JPEG, GIF or WebP images fit within 4 MiB total. Drafts are scoped to user, workspace, sender and thread; a delayed send cannot erase a newer draft in another thread. The UI uses the server's sender decision and requires human takeover when the existing campaign workflow is active. `POST /api/unibox/sync` processes a bounded batch for the selected account or permitted owned accounts. Resume cursors are stored server-side; the UI repeats while partial, accumulates batch counts, and can cancel without losing the checkpoint. Imported history is deduplicated in Max's current store. Attachment reads and channel details pass the same tenant/source checks as messages; no provider key reaches the browser. See the source repository's `docs/connection-authority.md` for the explicit-map, dry-run historical connection importer. No conversation history is silently copied or reassigned during this release. ## Deployment - **Vercel** — auto-deploy on push to `main`, preview deploys per PR. - **Cron:** read the committed `vercel.json` for schedules. Apollo/Sophie fallback, Hub maintenance and deployment sweep remain; messaging import runs in the current Max service. - **Local dev:** `pnpm dev` → http://localhost:3005. ### Environment variables (names only) - **Hub Supabase:** `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY` - **Agent Supabase:** `SOPHIE_SUPABASE_URL`, `SOPHIE_ANON_KEY`, `SOPHIE_SERVICE_ROLE_KEY`, `MAX_SUPABASE_URL`, `MAX_ANON_KEY`, `MAX_SERVICE_ROLE_KEY`, `CLAIRE_SUPABASE_URL`, `CLAIRE_SERVICE_ROLE_KEY` - **Stripe:** `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`, `STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `NEXT_PUBLIC_PRICE_ID_{MONTHLY,YEARLY}_{STARTER,BUSINESS}` - **Agent frontends:** `NEXT_PUBLIC_AI_SOPHIE_URL`, `NEXT_PUBLIC_AI_CLAIRE_URL`, `NEXT_PUBLIC_AI_MAX_URL`, `NEXT_PUBLIC_AI_ANDRE_URL` - **Claire services:** `CLAIRE_MCP_URL`, `CLAIRE_API_URL`, `CLAIRE_INTERNAL_SECRET`, `SERVICE_AUTH_SECRET` - **OpenWebUI:** `OPEN_WEBUI_URL`, `NEXT_PUBLIC_OPEN_WEBUI_URL`, `OPENWEBUI_JWT_SECRET` - **Third-party:** `UNIPILE_API_KEY`, `UNIPILE_DNS`, `UNIPILE_API_URL`, `APOLLO_API_KEY`, `OPENROUTER_API_KEY`, `SCRAPECREATORS_API_KEY`, `SCRAPECREATORS_API_URL`, `DEEP_SEARCH_WEBHOOK_URL` - **Reports:** `REPORT_MODEL`, `REPORT_SENDER_ACCOUNT_ID`, `REPORT_SENDER_EMAIL`, `REPORT_SENDER_NAME` - **App/infra:** `NEXT_PUBLIC_SITE_URL`, `NEXT_PUBLIC_APP_URL`, `CRON_SECRET`, `ADMIN_API_KEY`, `INSIGHTS_CACHE_TTL_DAYS`, `INSIGHTS_MAX_PER_IP_PER_HOUR`, `PROFILE_CACHE_TTL_DAYS`, `PROFILE_MAX_PER_IP_PER_HOUR` The repository `.env.example` is the configuration reference. This release also requires fixed `MAX_API_URL`, the current Max database configuration and the required Unipile webhook secret. Apply additive migrations and reissue scoped agent tokens before enabling consumers. TypeScript, lint and security/database tests are mandatory CI gates; production builds do not bypass errors. --- # Max Browser Extension — "Digital Crew — Prospect Extractor for LinkedIn" The Chrome (MV3) extension that puts Max inside LinkedIn. It is the top of the self-serve acquisition funnel: a genuinely useful free tool that upsells into a connected Max workspace. - **Repo:** [`max-browser-extension`](https://github.com/Digital-Crew-Technologies/max-browser-extension) - **Install guide:** `https://max.digitalcrew.tech/extension` - **One-click connect:** `https://max.digitalcrew.tech/extension/connect` ## What it does | Tier | Features | |---|---| | **Free (no account)** | Extract any LinkedIn member profile (name, headline, company, about, experience, education, skills, counts) → 21-column CSV export or clipboard copy. Entirely client-side. | | **Connected (free account + auto-provisioned API key)** | Save prospects to lists, contact enrichment (email & phone inline), AI message suggestions, AI-generated outreach campaigns created + launched from the panel, Unipile LinkedIn cookie connect. | Premium buttons are always rendered in free mode — greyed out with a one-click connect CTA — so the upgrade path is visible at the moment of intent. ## One-click connect flow 1. Any connect CTA (panel lock hint, popup, options) opens `/extension/connect?src=` on the Max instance. 2. Signed-out users pass through `/auth` (email/password or Google) and come straight back. 3. The page calls `POST /api/v1/api-keys/extension-connect` (JWT-only): mints — rotating any previous one — a key named **"Browser extension"** scoped to exactly `prospects:read/write`, `prospect-lists:read/write`, `campaigns:read/write`. 4. The key is handed to the extension via the presence bridge (`DC_EXT_CONNECT` postMessage → background worker validates it against the origin → `DC_EXT_CONNECT_RESULT`). If the handoff fails the key is shown once for manual paste. Re-running connect rotates the key, so it doubles as the lost/leaked-key recovery path. Keys are revocable in Max under **Settings → API keys**. ## Funnel telemetry The extension reports anonymous, allow-listed event names (with a random install id, never page content) to `POST /api/v1/extension/telemetry` (unauthenticated, validated, throttled). Events land in the Max Supabase `extension_events` table (RLS on, service-role writes only): `installed`, `panel_opened`, `csv_exported`, `csv_copied`, `lock_cta_clicked`, `connect_completed`, `first_save`, `first_enrich`. Core funnel metric: **connect rate** = `connect_completed` / `installed`, then activation (`first_save` / `first_enrich` within 24 h). ## Architecture notes - MV3, Vite + CRXJS, TypeScript strict, zero runtime dependencies. - The background service worker is the only context holding the API key and LinkedIn cookies; content scripts talk to it over typed messages. - Static host permissions: LinkedIn, Unipile hosted-auth, and the hosted Max instance. Self-hosted instance origins are granted at runtime from Options (Advanced). --- # Crew Creation Feature — Audit (July 2026) Scope: the full crew creation flow in `digitalcrew-orchestrator` — the Crew Studio wizard (`app/admin/crew-builder/`), the deploy API (`app/api/crew-builder/deploy/**`), the crew roster resolution (`app/api/crew/selected`), and the provisioning/bootstrap library (`lib/crewBuilder/**`, shared pieces of `lib/workerBuilder` and `lib/deploy/bundle.ts`). ## Architecture (as audited) The wizard auto-provisions one Hetzner VPS per crew the moment the Create step opens (`CreateServerStep.tsx` fires `POST /api/crew-builder/deploy` with no button press). The route sanitizes the team (`sanitizeTeam` → `sanitizeWorker` per agent), inserts a `worker_deployments` row (kind `crew`), and boots the box with a compact cloud-init that writes only `.env` + a launch watcher, then fetches the compose project as a self-extracting bundle (`GET …/deploy/{id}/bundle`, per-deployment bearer token). Boot is two-phase: control plane + runner at Create; the agents start only after `POST …/deploy/{id}/launch` flips `crew.launched`, which the box's watcher polls via `…/launch-status`. Status/teardown logic lives in `GET …/deploy/{id}`. ### Things done well - Constant-time bundle-token comparison and uniform 404s on the machine-facing routes (`bundle`, `launch-status`) — no existence or failure-mode leaks. - Secrets ride cloud-init `user_data`, not the bundle channel (with one exception, M1); bundle serving is gated to `provisioning`/`installing` and < 24 h. - The managed OpenRouter key is entitlement-gated **server-side** (`hasManagedAccess`) both at Create (fallback key) and in the model route — it can't reach an unentitled box. - Label-based server adoption (`deployment=`) recovers creates that outlive the route's `maxDuration`; credentials are persisted *before* the Hetzner call so adoption never strands them. - Failure paths tear down the VM and the auto-registered runner rows; placement and resource-limit errors surface as actionable messages. - Launch is idempotent, owner-checked, and reconciles against the create-time roster so the stored config can't describe services the running stack doesn't have. - The bundle extraction dir is pinned to the create-time slug, so renaming the manager mid-wizard can't strand the config bundle in a directory compose never reads. - User input is centrally hardened (`sanitizeWorker`: control-char stripping, length caps, catalog-filtered MCP/integration ids) and YAML/compose interpolation is quoted (`quote()` in `renderCrewCompose`); cloud-init interpolations are charset-validated (`generateCrewCloudInit.ts:279-288`). ## Findings ### High **H1 — No server-side quota, billing gate, or duplicate guard on VPS provisioning.** `POST /api/crew-builder/deploy` requires only a valid Supabase session. There is no entitlement check for the *server itself* (only for the managed LLM key), no per-user cap, no rate limit, and no check for an already-active crew deployment. Every call creates a new `worker_deployments` row and a new Hetzner server (cx33 up to cx53, 16 vCPU/32 GB). Deduplication exists only client-side (`autoCreateFired` ref + the GET-resume on mount). Direct API calls, multiple tabs, or an error-retry loop create unlimited servers billed to the platform's Hetzner account — until the project's server limit is hit, which then breaks deploys for *all* customers (the route's own `server_limit` error path documents this failure mode). *Recommendation:* before insert, reuse the exact query the resume `GET` already runs (active crew, < 24 h) and return that deployment instead of creating a second one; add a per-account concurrent-deployment cap and tie provisioning to an entitlement or plan. **H2 — Abandoned servers are never torn down; cleanup depends on the owner's browser.** The launch-window (70 min) and setup-deadline (45 min) enforcement — and the `deleteServer` teardown — run only inside `GET /api/crew-builder/deploy/[id]` (`[id]/route.ts:236-279`), which is called by the wizard's 6-second poller. The server is created automatically before the operator confirms anything (H1 note: Create step auto-fires). If they close the tab between Create and Launch, nothing polls, the deadlines never evaluate, and the VM bills indefinitely. `vercel.json` has no cron that sweeps `worker_deployments` (the hub cron doesn't touch it). *Recommendation:* add a scheduled sweep that applies the same deadline rules server-side (fail + tear down never-launched crews past `LAUNCH_WINDOW_MS`, wedged installs past `SETUP_DEADLINE_MS`, and `provisioning` rows past the adoption deadline). ### Medium **M1 — The "bundle carries no secrets" invariant is broken by `.env.launch`.** `renderBundleScript` (`lib/deploy/bundle.ts:32-35`) documents that the bundle channel must never deliver secrets and skips `.env` — but the config-stage bundle ships `.env.launch` containing decrypted integration API keys (`bundle/route.ts:106-126`). A bundle token that leaks during the setup window therefore exposes live third-party credentials, not "only the crew's configuration" as the module comments and `crew-builder/README.md` claim. The token itself is exposed more widely than `.env` on the box (M2). *Recommendation:* either accept and document the weaker invariant everywhere, or deliver launch-time keys over a separate, single-use channel. **M2 — Bundle token handling on the box is looser than the secret it now protects.** The token is baked into `launch-watcher.sh` with mode **0755** (world-readable; `generateCrewCloudInit.ts:344-347` — contrast `.env` at 0600) and passed on `curl` command lines, visible in `/proc/*/cmdline` to any process on the host. Since M1 makes the token secret-bearing during launch, write the watcher 0700 and pass the token via a header file or environment instead of argv. **M3 — Every agent container mounts the host Docker socket.** `renderCrewCompose` adds `/var/run/docker.sock` to *each* agent service on managed deploys (`generateCrewProject.ts:231-252`), while the generated README tells operators "agents run commands inside Docker to sandbox risky work". The socket mount is the opposite of a sandbox: any prompt-injected agent (these agents process external email, prospect and meeting content by design) has root-equivalent control of the VM — it can read the shared `.env` (all integration keys, terminal password), other agents' data volumes, and the runner's credential volume. Blast radius is the operator's own box (single-tenant), but the sandbox claim is inverted. *Recommendation:* mount the socket only where the terminal backend genuinely needs it (or proxy it), and correct the README wording. **M4 — `integrations: undefined` semantics contradict between code and docs.** `sanitizeWorker` (comment at `sanitizeWorker.ts:36-38`) and `buildCrewMemberConfiguration` (`team.ts:93-95`) both document "undefined ⇒ default to everything the workspace has connected". The crew deploy route does the opposite — it forces `[]` ("integrations start EMPTY by design", `deploy/route.ts:207-218`) — and the launch route back-fills members from the manager's selection. The stale comments are the dangerous kind: a future caller relying on them would silently ship every connected key to a box. Align the comments (and verify the single-worker path's intended default) with the empty-by-design behavior. **M5 — Non-atomic read-modify-write of the `crew` jsonb column.** The deploy route writes `crew` three times (insert → runner-stamp update → error-path rewrite), and the launch route rewrites the whole object from its own earlier read. Interleavings can drop flags — e.g. a launch accepted between the deploy route's read and its error-path rewrite loses `launched`/`launched_at`, leaving the watcher polling a crew the app believes launched. Low likelihood, but a jsonb merge/patch (or promoting `launched`/`runner` to columns) removes the race class. **M6 — Vexa default API base is plaintext HTTP to a bare IP.** `DEFAULT_VEXA_API_BASE = 'http://72.62.189.93:8056'` (`generateCrewCloudInit.ts:92`) means any crew that selects the Vexa connector without an explicit override sends `VEXA_API_KEY` unencrypted across the internet. ### Low - **L1 — `appOrigin` trusts forwarded headers when `NEXT_PUBLIC_SITE_URL` is unset** (`deploy/route.ts:113-122`). The charset validation prevents shell breakout in cloud-init, but a spoofed host would still aim the box's bundle fetch (root-executed bash) at an attacker origin. Ensure the site URL is always set in production. - **L2 — Re-entering the wizard with a crew older than 24 h auto-provisions a second server**: the resume `GET` filters `created_at > now-24h`, so an existing *running* crew past that window isn't resumed and `CreateServerStep` auto-fires a new deploy. If one-crew-per-account is the intent, the resume lookup shouldn't age-limit `running` rows (compounds with H1). - **L3 — `OPENROUTER_API_KEY=${openRouterKey || apiServerKey}`** (`generateCrewCloudInit.ts:148`): the box's internal bearer doubles as the OpenRouter key placeholder. Works because agents only talk to the local runner, but a misconfigured base URL would send the box's API bearer to a third party. Prefer a distinct placeholder value. - **L4 — Stale doc:** `docs/crew-os.md` (this repo) still describes the CORS allowlist as including `*.vercel.app`; in code that's non-production only (`lib/auth/redirect-allowlist.ts`). Also `crew-builder/README.md`'s "the bundle carries no secrets" needs the M1 caveat. ## Suggested priority 1. H1 + H2 (cost abuse and unbounded billing are exploitable today with just an account). 2. M1 + M2 together (they compound), then M3. 3. M4/M5/M6 and the doc fixes as routine hardening.