Upstream Tooling & the Planned Agent Fleet
The data-acquisition layer that feeds CARR from outside the database, and the planned 7-agent fleet that internalizes it into Azure.
Upstream Data Agents & Custom Code
Download .docx09 - Upstream Data Agents + Custom Code (the "manage-the-DB-from-outside" layer)
The doc that answers "what runs OUTSIDE CARR's database to keep it fed, and where does that go on Azure." CARR's Supabase is not self-contained: a chain of SaaS tools and BC-owned custom code (on the bc-ops VPS, in Colab, and in the BC Hub) sources, scrapes, enriches, and syncs data into it. This inventory maps each piece and its Azure-internalization target, per the decision to bring the whole chain into BW's Azure tenant over time.
Triggers: "upstream agents", "sales navigator", "phantombuster", "clay", "fullenrich", "hub sync", "custom code", "external data pipeline", "internalize into azure".Excluded from scope: social listening (LinkedIn post/engagement monitoring) and the ELG team-activity feed are not part of this migration - never fully built on the BC side, not handed over. Omitted from this inventory by design; a possible separate future effort, not a migration deliverable.
Built 2026-07-21 from a live read of the CARR repo, bc-ops crontab, and the value-package docs. Companion to08_edge_function_census.md(the in-app code) and07_azure_sql_conversion.md(the in-database code).
The mental model
SOURCING SCRAPE/ENRICH SEND INTAKE-> CARR DB
Sales Navigator -> PhantomBuster -> \ clay-enrichment-callback
(search defs) (Sales Nav scrape) \ import_hub_contacts RPCs
-> Clay (Find-People sync_* Hub RPCs
+ enrichment) -> HeyReach send + webhooks
-> FullEnrich (emails) EmailBison send + webhooks
BC Hub DB (17,290-company master) --- seeds + syncs accounts/contacts/trials/grants/news
Almost none of this runs inside CARR's Supabase. It runs on BC infrastructure (the bc-ops VPS, Google Colab, and the BC Hub Supabase mjsgtszehjltxmbxtctz) and pushes into CARR (drzxmokguptvwptuqjcv) via webhooks and sync RPCs. That is the layer BW's team has the least visibility into today, and the one this doc exists to expose.
Clarification on Apollo: Apollo is not in CARR's active chain. It appears in the value-package only as a named alternative / potential email-waterfall backup. The live sourcing tools are Sales Navigator, PhantomBuster, Clay, and FullEnrich.
Part A - External SaaS tools
Each row is a third-party product BC operates on CARR's behalf. The Azure column is the internalization target (a BW-owned Azure-native equivalent or an Azure-hosted shim), per the decision to own the whole chain eventually.
| Tool | Role in CARR | How it touches CARR today | BC account / boundary | Azure internalization target | Notes |
|---|---|---|---|---|---|
| Sales Navigator | Lead sourcing: search definitions per modality / seniority / department | Indirect - defines the audience PhantomBuster scrapes | LinkedIn seat (BC) | No Azure equivalent (it is LinkedIn's product). Stays a licensed input; the search definitions become config in an Azure-hosted job | The human-defined search criteria are the real asset; document them as config |
| PhantomBuster | Scrapes Sales Nav results into CSVs of LinkedIn profiles | Runs at the Hub; output lands in Hub new_contacts (import_source=phantombuster), then syncs to CARR (~81K contacts, the workhorse) |
BC PhantomBuster (credit system) | Azure Function / Container App calling PhantomBuster's API, or replace with an Azure-hosted scrape agent. Scheduling -> Timer trigger | Credit-metered; keep the vendor behind an Azure job first, replace later |
| Clay | (a) Find-People: domains -> people + waterfall email; (b) contact enrichment (headline, summary, experience) | Push scripts on bc-ops send candidates to Clay; Clay posts back to clay-enrichment-callback (CARR, by contact_id) + Hub webhook (by slug) |
BC Clay account. Canonical BioCreative webhook 6848f75f-.... NEVER the Made webhook abc4cda3-... (credit-pool + wrong-DB leak class) |
Azure Function as the push driver + the existing callback re-hosted as an Azure HTTP Function. Enrichment logic could move to Foundry agents later | Routing discipline is a hard rule; see docs/runbooks/clay_enrichment_routing.md |
| FullEnrich | Email find + verify (waterfall) | Hub-side finds sync into CARR via sync_carr_emails.py (every 20 min) + push_carr_email_finds.py |
BC FullEnrich account | Azure Function (timer) driving the find/verify + Hub->CARR sync | Same push+poll+sync pattern as Clay |
| HeyReach | LinkedIn outreach send platform | 8 EFs (send, sync accepted/replies/stats, webhook, provision, control, lists) | BC HeyReach, CARR workspace only | Stays a SaaS send platform; the 8 integration EFs move to Azure Functions (see 08). Key -> Key Vault |
One workspace per client is a hard rule |
| EmailBison | Email outreach send platform | 9 EFs (sync, push, webhook, reply, forward, reconcile, 3 admin) | BC-hosted send.biocreativestrategies.com, CARR = workspace 4 |
Stays a SaaS send platform; the 9 integration EFs move to Azure Functions (see 08). Key -> Key Vault |
Workspace-confinement guard must survive the move |
Part B - BC custom code (cross-repo + VPS)
These are BC-authored scripts and notebooks that read/write CARR's database from outside it. They are the "agents we developed upstream to manage their database."
B1 - CARR repo: Colab notebooks (scripts/colab/)
Run manually in Google Colab against the CARR DB. These built and continue to maintain the account + contact universe.
| Notebook / script | Job | Azure target |
|---|---|---|
carr_account_import_v1.ipynb |
Clean + load account CSVs via contacts_staging |
Azure ML notebook / Azure Function + Blob intake |
carr_contact_import_v1.ipynb |
Contact CSV import via staging + linkedin_url dedupe | Azure ML notebook / Function |
carr_account_matcher.py |
Match CARR list vs Hub (exact + fuzzy) | Azure Function / batch job |
carr_unified_classifier_v1.ipynb |
AI account classification (category + subcategory) | Foundry agent / Azure ML |
carr_account_enrichment_v1.ipynb |
CARR-specific deep enrichment into carr_enrichment_data |
Foundry agent / Azure ML |
carr_contact_classifier_v4.ipynb + run_classifier_v4.py |
L1/L2 contact seniority + buyer-segment classification | Foundry agent / Azure Function |
carr_clay_import_v2.ipynb / carr_clay_push_v2.ipynb |
Clay import + push orchestration | Azure Function (pairs with Clay row above) |
carr_batch_email_generator_v1.ipynb |
Batch email drafting (overlaps batch-generate-outreach) |
Fold into the Azure AI Function |
carr_jeff_batch_enrichment.ipynb, carr_geo_enrichment_590.ipynb |
One-off enrichment batches | Ad-hoc; re-run as Azure notebooks if needed |
B2 - CARR repo: enrichment + sync scripts
| Path | Job | Azure target |
|---|---|---|
scripts/clay_enrichment/*.py (clay_enrich_push, backfill, recover_*) |
Clay push driver + recovery/backfill utilities | Azure Function(s) |
scripts/enrichment/sync_carr_emails.py |
FullEnrich email sync Hub->CARR (bc-ops cron, 20 min) | Azure Function (timer) |
scripts/enrichment/push_carr_email_finds.py |
Push email-find candidates to FullEnrich | Azure Function |
scripts/sync_to_carr.py |
PhantomBuster contact sync Hub->CARR via import_hub_contacts RPC |
Azure Function (timer) |
B3 - bc-ops VPS crons (verified live 2026-07-21)
| Cron | Schedule | Job | Azure target |
|---|---|---|---|
carr_burst_classify_seed.sh |
every 30 min (flock) | Classify newly enriched contacts + seed evergreen campaigns | Azure Function (timer) / Container App job |
sync_carr_emails.py |
every 20 min (flock) | FullEnrich email sync Hub->CARR | Azure Function (timer) |
clay_enrich_push.py |
manual | Push Tier 1-3 contacts to Clay (BC webhook 6848f75f) |
Azure Function |
| Cortex evergreen plan mirror | hourly | BC-internal (not CARR-owned) | Stays BC-side; document as a boundary |
B4 - The Hub DB dependency (the biggest one)
CARR leans on the BC Hub (mjsgtszehjltxmbxtctz, 17,290-company master DB) as its "external brain." These RPCs run against CARR but read from the Hub:
sync_cgt_accounts_from_hub()(weekly) - new CGT companiessync_clinical_trials_from_hub(),sync_grants_from_hub()(weekly) - research signalssync_news_from_life_science()(4h) - newssync_academic_labs_from_hub(),sync_academic_pis_from_hub(),sync_academic_hierarchy_from_hub()- academic intelligenceimport_hub_contacts/import_hub_contacts_v2- contact intake from PhantomBuster + Clay Find-People
This is the single largest open decision for the Azure cutover. Two paths:
- Keep the Hub BC-run, feed Azure. BC continues to run the Hub and pushes into CARR's Azure DB via an API / scheduled copy. Lowest effort; CARR stays dependent on BC infra.
- Internalize the Hub feed. BW ingests the relevant Hub subset into Azure (one-time seed + periodic refresh via Data Factory), so CARR's Azure DB is self-sufficient. Higher effort; removes the BC dependency.
Flag this to BW explicitly. It is not an in-app code question, it is a data-supply-chain decision that affects whether CARR can run standalone on Azure.
Internalization sequence (recommended)
- Land the webhooks + syncs on Azure Functions first (they are the CARR-side halves already in the
08census). Low risk, high visibility. - Re-host the bc-ops crons (B2/B3) as Azure timer Functions / Container App jobs, reading keys from Key Vault.
- Wrap the SaaS drivers (PhantomBuster, Clay, FullEnrich push) as Azure Functions calling the vendor APIs - keep the vendors, move the orchestration into BW's tenant.
- Decide the Hub dependency (B4) - this gates whether CARR is standalone on Azure.
- Port the Colab notebooks (B1) to Azure ML notebooks / Foundry agents last - they are manual and least time-critical.
Target-state: the CARR agent fleet (who owns each piece after)
The internalization above becomes a 7-agent CARR fleet on Azure (full spec: 11_carr_agent_fleet.md). Each upstream piece maps to the agent that replaces it:
| Upstream piece (this doc) | Replaced by (agent in 11) |
|---|---|
BC Hub feed + sync_*_from_hub RPCs + life-science API matching |
Agent 1 - Ingestion / API (phased: keep Hub -> direct ClinicalTrials/NIH/SEC/PatentsView pulls) |
Sales Navigator + PhantomBuster sourcing (sync_to_carr.py) |
Agent 1 - Ingestion / API |
Clay Find-People + enrichment + FullEnrich (clay_enrich_push.py, sync_carr_emails.py, clay-enrichment-callback) |
Agent 2 - Enrichment |
carr_account_matcher.py + fuzzy match + new-account creation |
Agent 3 - Account-Matching (HITL) - Made stakeholder-registry resolver pattern |
carr_unified_classifier_v1, carr_contact_classifier_v4, run_classifier_v4 |
Agent 4 - Classification |
generate-outreach-message, batch-generate-outreach |
Agent 5 - SSO / Outreach (SpanCorr + Made superset) |
generate-content (CCC / Content Studio) |
Agent 6 - Content Authoring (sender-voice posts; drafts only, human-published, no auto-posting) |
bc-ops crons + manual Colab orchestration (carr_burst_classify_seed.sh) |
Agent 7 - Orchestrator / Conductor (propose-only) |
Runtime is hybrid (Foundry identity + Model Router + observability, portable agent logic on Foundry Hosted / Container Apps). See 11_carr_agent_fleet.md for build specs, HITL flows, sequencing, and open decisions.
Cross-references
-
Planned agent fleet that internalizes this whole layer:
11_carr_agent_fleet.md -
In-app edge functions:
08_edge_function_census.md - In-database code:
07_azure_sql_conversion.md - Component crosswalk:
05_carr_migration_map.md - Clay routing discipline:
docs/runbooks/clay_enrichment_routing.md - Contact discovery detail:
docs/value-package/06_contact_discovery.md - FullEnrich runbook:
docs/runbooks/fullenrich_email_find.md
Planned CARR Agent Fleet
Download .docx11 - CARR Planned Agent Fleet (Azure-internalized replacement for the upstream BC layer)
The target-state design that turns the upstream inventory in
09into CARR-owned Azure agents. Today CARR is fed by BC-run SaaS + custom code + the BC Hub (see09_upstream_data_agents.md). This doc specs a 7-agent fleet that internalizes that whole chain into Barry-Wehmiller's Azure tenant, running hybrid (Microsoft Foundry for infrastructure, identity, and model routing + portable agent logic on Foundry Hosted Agents / Container Apps).
Triggers: "carr agent fleet", "replace upstream agents", "ingestion agent", "account matching agent", "sso agent", "hitl", "foundry agents for carr", "internalize the pipeline".
Status: PLANNED / TARGET-STATE. Nothing here is live. This is a design for BW, built 2026-07-21, modeled on the SpanCorr fleet plan + SSO agent, the Made stakeholder-registry HITL surface, and the BC Cortex conductor pattern.
Why this fleet exists
09 documented that CARR's database is kept alive by pieces that run outside it: Sales Navigator + PhantomBuster (sourcing), Clay + FullEnrich (enrichment), the Colab notebooks (import / match / classify / enrich), the bc-ops crons, and the BC Hub feed. Under the "internalize into Azure" decision, each of those becomes a named agent that BW owns. The fleet is the concrete answer to "who runs the pipeline after CARR leaves BC's infrastructure."
Runtime posture (hybrid)
Per 02_ai_foundry_agents.md, the recommended posture is:
- Foundry provides: Entra Agent ID (each agent is a governed non-human identity under Zero Trust), the Model Router (one endpoint across providers, cost routing), Foundry Evaluations / observability, VNet, and Key Vault integration.
- We provide: agent logic in a portable framework (Pydantic AI or LangGraph, as SpanCorr and BC Cortex already run) hosted on Foundry Hosted Agents or Azure Container Apps, so the logic is not locked to a preview control-plane.
- State + memory: Azure SQL (the migration target, see
07) for job/run state; pgvector or Foundry IQ for any RAG/memory the SSO + classification agents need. - Discipline: propose-only + HITL by default (mirrors BC Cortex task-capture and the Made CHIEF posture). Agents never auto-commit high-impact writes; they emit proposals to a review queue.
The fleet at a glance
| # | Agent | Replaces (from 09) |
HITL | Harness | State | Azure landing |
|---|---|---|---|---|---|---|
| 1 | Ingestion / API | BC Hub feed + sync_*_from_hub RPCs + Sales Nav / PhantomBuster sourcing + sync_to_carr.py |
Low-confidence company-match queue | Pydantic AI / LangGraph | DB-backed (ingestion_runs) |
Foundry Hosted Agent, timer-triggered; phased Hub -> direct API |
| 2 | Enrichment | Clay Find-People + enrichment + FullEnrich (clay_enrich_push.py, sync_carr_emails.py, clay-enrichment-callback) |
Optional review on conflicting fields | Pydantic AI | Persistent / checkpointed (enrichment_runs) |
Azure Container Apps job |
| 3 | Account-Matching (HITL) | carr_account_matcher.py + fuzzy match + new-account creation |
Yes - resolution review queue (Made pattern) | Pydantic AI | DB-backed proposals | Foundry Hosted Agent + Model Router |
| 4 | Classification | carr_unified_classifier_v1, carr_contact_classifier_v4, run_classifier_v4 |
Spot-review low-confidence | Foundry agent | Stateless per record | Foundry Hosted Agent + Model Router |
| 5 | SSO / Outreach | generate-outreach-message, batch-generate-outreach |
Message QA gate before send | Portable (SpanCorr+Made core) | Sessioned + memory | Container Apps + Model Router |
| 6 | Content Authoring | generate-content (CCC / Content Studio) |
Yes - human reviews + posts every draft | Portable (SpanCorr+Made core) | Sessioned + memory | Container Apps + Model Router |
| 7 | Orchestrator / Conductor | bc-ops crons + manual Colab orchestration (carr_burst_classify_seed.sh, etc.) |
Propose-only + confirm gates | Pydantic AI (MCP host) | DB-backed jobs (conductor_jobs) |
Container Apps + Entra Agent ID |
Cross-cutting for every agent: Entra Agent ID, secrets from Key Vault, LLM via Foundry Model Router, telemetry to Foundry Evaluations, and a daily/monthly cost cap guardrail (as the SpanCorr SSO agent already enforces).
The three agents you named, in depth
Agent 1 - Ingestion / API (phased Hub -> direct API)
Replaces CARR's single largest external dependency: the BC Hub feed. Runs in two phases so nothing breaks at cutover.
- Phase 1 (keep the Hub, re-host the feed): the agent runs the existing
sync_*_from_hublogic as an Azure timer job reading from the BC Hub and writing CARR's Azure DB. Lowest risk; CARR still depends on BC infra but the scheduler is BW's. - Phase 2 (retire the Hub): the agent pulls the life-science APIs directly into CARR's Azure DB:
- ClinicalTrials.gov (trials by sponsor), NIH RePORTER (grants by org), SEC EDGAR (filings), PatentsView (patents) - the four public sources the Hub matched today.
- Sourcing (Sales Nav search definitions -> PhantomBuster or an Azure-hosted scrape) lands here too.
- Match-on-ingest: every inbound record is resolved to a canonical CARR company; low-confidence matches go to the Account-Matching agent's review queue rather than auto-creating duplicates.
- Cutover point: Phase 2 goes live per-source as each direct pull is validated against the Hub feed; the Hub feed for that source is retired once parity is proven.
Agent 3 - Account-Matching (HITL), modeled on Made's resolver
CARR's carr_account_matcher.py did exact + fuzzy matching offline. The Azure agent makes this a live, governed, human-reviewed surface, copied from Made's Stakeholder Registry resolver (useStakeholderResolution.ts + er_resolution_proposals):
- Inbound external references (from the Ingestion agent: trial / nih / sec / patent / news / lead / event) are resolved to a canonical
companiesrow with a confidence score + reasoning + evidence written to a proposals table (mirrorer_resolution_proposals). - Confidence bands drive routing: high (>= 0.85) can auto-confirm; medium (0.6-0.85) and low (< 0.6) land in a review queue for a human to accept/reject (mirror
accept_er_resolution_proposal/reject_er_resolution_proposal+v_er_resolution_stats). - Two distinct surfaces, same as Made: (a) this external-reference resolver (life-sci APIs -> canonical company), and (b) an internal dedup surface for CARR's own imported lists (the analog of Made ID /
made_ralph_proposals). Both are propose-only withrequires_confirmation+confirmed_byaudit columns. - This is the "stakeholder registry HITL element" applied to CARR's new + life-science account-matching.
Agent 5 - SSO / Outreach (superset of SpanCorr + Made)
CARR's generate-outreach-message + batch-generate-outreach become one context-engineering agent, built as a superset of our two most advanced SSO builds. (Content authoring - generate-content - is now a separate Agent 6, below.)
- From SpanCorr
spancorr-sso-agent(the 16-module core):hydrate(self-hydrate account/contact from DB),persona-resolver,matrix-resolver(pain/value cells),title-classifier,selector(select-then-write a single pain+capability pair, no grab-bags),positioning-loader,sender-loader(+ signature + style memory),memory(semantic recall + conversation turns),prompt-builder,offer-loader,sanitizer(+ prompt-injection neutralization),validator, plus the daily/monthly cost caps and heartbeat telemetry. - From Made
sso-agent: the one-agent principle (one agent for every message type), live skill fetch from the repo (skills editable without redeploy, ~60s cache), the committed FORMAT_SPECS discipline, per-type MESSAGE_LIMITS (LinkedIn connection 280, InMail 1900, follow-up 600, cold emails taste-capped), banned-vocab enforcement, and the em/en-dash sanitizer. - HITL: generated messages land as
ready_to_sendbehind a QA gate before the send agents (HeyReach / EmailBison integration functions, see08) push them - preserving CARR's existing human-approval step.
The other three agents (brief)
- Agent 2 - Enrichment: waterfall enrichment (contact + email) replacing Clay + FullEnrich. Persistent/checkpointed runs so a long batch resumes; writes back only into empty cells (the
clay-enrichment-callback"don't-clobber" rule). Vendors can stay behind the agent initially, then be swapped for Azure-native providers. - Agent 4 - Classification: account category/subcategory + contact seniority / buyer-segment / marketing-persona, replacing the three classifier notebooks. Foundry agent + Model Router (route routine classification to a mini/Haiku-tier model). Low-confidence rows spot-reviewed.
- Agent 6 - Content Authoring: maps to the current
generate-contentEF (CCC / Content Studio). It drafts LinkedIn / blog posts in a chosen sender's voice, authored from the brand voice +outreach_positioningcontext - not from any social-listening feed. Drafts only: a human reviews and publishes every post. There is no automated posting and no social listening in scope; automated publishing / social monitoring is explicitly out of scope for this migration and would be a separate future effort if ever wanted. - Agent 7 - Orchestrator / Conductor: an MCP-host router (mirror the SpanCorr Conductor / BC Cortex) that sequences the fleet: ingest -> match -> classify -> enrich -> (on demand) generate messages + content. Propose-only, DB-backed jobs, heartbeats, HITL confirm gates. It holds no domain logic; it routes and tracks.
How the CARR fleet uses the BC Agent-Building Stack
Every agent above is built from the same reusable stack BioCreative uses across its fleets - the full method is in 16_agent_building_stack.md (the guide shared with the BW/CARR team on 2026-07-21). Each agent is one stack (processor + harness + shape + skills + context + memory + eval + trigger), where shape = loop-depth x swarm-width. Mapping the seven CARR agents onto that controlled vocabulary:
| Agent | Loop-shape | Swarm | House pattern | HITL gate | Key reusable patterns |
|---|---|---|---|---|---|
| 1 Ingestion / API | one-shot per record, continuous tick overall |
single | Signal-Desk + Scheduled Worker + Console | low-confidence match queue | deterministic registry ingest -> prefilter/dedup -> bounded reasoning -> human gate; match-on-ingest |
| 2 Enrichment | bounded-loop (checkpointed, resumable) |
single (parallel-fan for big batches, after a small-slice probe) |
Scheduled Worker + Console | optional review on field conflicts | checkpointed run state; "don't-clobber" write rule; stop-condition set |
| 3 Account-Matching | one-shot per record -> proposal |
single | propose-only + confirm gate (Made resolver) | yes - confidence-banded review queue | confidence score + evidence; requires_confirmation / confirmed_by audit |
| 4 Classification | one-shot per record |
parallel-fan (batch, probe first) |
Model-Router-fronted | spot-review low-confidence | route routine work to a mini/Haiku-tier model (Layer 0 substrate); stateless per record |
| 5 SSO / Outreach | bounded-loop (select-then-write -> sanitize -> validate, maker/checker) |
single | Brain-API / Agent-Service | message QA gate before send | self-hydrate; select-then-write (no grab-bags); sanitizer + validator; daily/monthly cost caps; heartbeat |
| 6 Content Authoring | bounded-loop (draft -> validate) |
single | Brain-API / Agent-Service | yes - human reviews + posts every draft | sender-voice from positioning; drafts only, no auto-posting; banned-vocab + dash sanitizer |
| 7 Orchestrator / Conductor | continuous (ticks) |
supervisor | Conductor (MCP host) | propose-only + confirm gates | routes/tracks only, no domain logic; DB-backed jobs; Heart-API heartbeats |
Cross-cutting (from the guide, enforced on every agent):
- Every loop that is not one-shot carries the mandatory stop-condition set - max_turns, per-run dollar cap, duplicate-call detection, wall-clock timeout, and an explicit "I'm stuck" exit. A loop without a codified breaker is not done.
- Registered in the MCS so the rest of the fleet can find each agent (status + priority + host).
- Secrets from Key Vault, LLM via the Foundry Model Router, telemetry via heartbeat (the Heart-API / eval layer), matching the guide's Layer-0/Layer-2 house patterns.
- The two principles run throughout: the Karpathy principle (default to strong, well-supported methods; keep the model swappable) and propose-only + HITL by default (agents never auto-commit high-impact writes).
See 16_agent_building_stack.md for the full stack, the loop/swarm/memory/eval vocabularies, and the selection method.
Sequence + prerequisites
- Prereqs (from the rest of the pack): the Azure SQL database (
07) and Entra identity (04) must exist first - the agents write to the DB and carry Agent IDs. - Land the deterministic agents first: Ingestion Phase 1 + Account-Matching + Classification (they rebuild the data supply with the least model risk).
- Then Enrichment (vendor-backed), then SSO + Content Authoring (both need positioning + sender data in place).
- Orchestrator last - it can only route a fleet that exists (same rule as the SpanCorr fleet plan: package capabilities before the conductor).
- Ingestion Phase 2 (retire the Hub) runs per-source after parity is proven.
Open decisions for BW
- Agent host: Foundry Hosted Agents vs Azure Container Apps for the portable logic (cost + ops tradeoff).
- Observability: Foundry Evaluations only, or add Pydantic Logfire (as SpanCorr runs) for parity with our other fleets.
- Review-queue ownership during transition: who works the Account-Matching + message-QA queues while BW ramps - BC, CARR reps, or BW ops.
- Hub retirement pace (Agent 1 Phase 2): which life-science sources BW wants pulled directly first, and whether any stay on the BC Hub long-term.
- Vendor retention (Agent 2): keep Clay/FullEnrich behind the Enrichment agent, or move to an Azure-native enrichment path.
Cross-references
- The upstream layer this fleet replaces:
09_upstream_data_agents.md - Foundry / Model Router / Agent ID:
02_ai_foundry_agents.md - Edge functions the SSO + send path relies on:
08_edge_function_census.md - Database + identity prerequisites:
07_azure_sql_conversion.md,04_github_devops_governance.md - Whole-system view:
../CARR_FULL_SYSTEM_MIGRATION_MAP.md - Reference builds: SpanCorr
architecture/agent-fleet/SPANCORR_AGENT_FLEET_PLAN.md+services/spancorr-sso-agent/; Madeskills/agents/sso-agent/+ Stakeholder Registry resolver.
The BioCreative Agent-Building Stack
Download .docx16 - The BioCreative Agent-Building Stack
How BioCreative builds AI agents - the full stack, the controlled vocabulary, and the method for choosing each piece. Shared with the CARR + Barry-Wehmiller migration team (2026-07-21) so the agents in the planned CARR fleet are legible and repeatable. Companion to
11_carr_agent_fleet.md(which maps each CARR agent to these patterns).
Triggers: "how we build agents", "agent stack", "harness", "loop", "swarm", "agent methodology", "building stack guide".
The thesis in one paragraph
Building the first working version of an AI agent should not be an art
project. The LLM is the cheap, swappable part; BioCreative's leverage is
the software we design around it. Every agent is the same
eight-piece stack — a processor (model) + a harness (its
environment) + a shape (loop-depth × swarm-width) + skills (what it can
do) + context (what it knows) + memory (what it keeps) + eval (how we
know it works) + a trigger (how it wakes up) — registered in the
Modular Context System (MCS) so the rest of the fleet can find it.
Choosing each piece follows a decision tree you could hand a junior
engineer or an AI. The genuinely hard, valuable work starts after the
agent exists: tuning judgment, controlling cost, evaluating output, and
wiring agents together. This guide lays out the full stack, the
controlled vocabulary for each dimension, and the selection algorithm
that turns "build me an agent that does X" into a fast, confident first
build.
Part 1 — The mental model
The stack (one spine, eight pieces)
processor → harness → shape → skills → context → memory → eval →
trigger — all tracked in the MCS.
The product spine that frames why we build (Positioning → Scoring →
Data → Skills → Agents → Fleet) sits above this; this guide is the
Agents rung — how one agent is assembled.
An agent is not a monolith. Three ideas are easy to confuse, so we
define them precisely:
| Concept | Plain definition | Example |
|---|---|---|
| Harness | A thing you *build on* — a framework, runtime, or service. The "chassis." | Pydantic AI; the Archon engine; an Edge Function. |
| Archetype | A reusable *recipe* that composes several harnesses into a known agent *kind*, with named variants. | "Chatbot," "research agent," "outreach agent." |
| Build | A concrete *shipped* agent — an instance of an archetype, assembled from harnesses. | Cortex Conductor; a client's Second-Brain chatbot. |
In one line: an agent is built on a harness, has a shape,
wields skills, knows context, keeps memory, is watched
by eval, is invoked by a trigger, runs on a host, and sits at a
status with a priority.
Layers vs dimensions (don't conflate)
Two different axes organize the stack:
Layers answer "what kind of thing is this harness?" — a stack,
from raw model up:
| Layer | What lives here | Role |
|---|---|---|
| 0 — Model substrate | Provider-agnostic routing + local inference | The engine block |
| 1 — Generic harnesses | Third-party frameworks/runtimes/tools we build *on* | The chassis options |
| 2 — House patterns | Named conventions we compose from Layers 0–1 | Our standard body styles |
| (builds) | Concrete shipped agents — live in the fleet registry, not the menu | The finished vehicles |
Dimensions answer "how is this build wired / what does it retain /
how do we measure it?" — orthogonal to layers, tagged on the build
(matrix row), never a new layer:
-
Shape = loop-depth × swarm-width (Parts 3–4)
-
Memory = types × write-path × store (Part 5)
-
Eval = observability floor tag (Part 6)
A Layer-1 harness like Pydantic AI can run several shapes and carry
different memory/eval; a Layer-3 build picks specific ones. Never
add a "loop", "swarm", "memory", or "eval" layer — these are
dimensions on the build.
Two principles that run through everything
-
The Karpathy principle — "Six months is an eternity in this
space." Default to strong, well-supported methods (Pydantic AI,
FastAPI, Postgres, LiteLLM); refine one house method rather than chase
every hyped framework. New harness → 30-day skeptical window before it
earns a slot. We build a consistent system, not a museum. -
Compose, don't monolith — a real build is a stack across layers;
a "lesser" harness (a workflow tool, a serverless function) is usually
best used as a tool inside a fuller agent, not the whole thing.
Legend used throughout: ✅ in production · 🧪 evaluated · ⚪ on shelf ·
❌ rejected.
Part 2 — The harnesses (Layers 0–2)
Each harness follows the same shape: what it is · how it works · what
it's good for · when we don't · cost. Memory-native / eval-native
notes are appended where relevant (full table in harnesses/INDEX.md
§"Memory / eval capability").
Layer 0 — Model substrate
Model Router (LiteLLM proxy) ✅ — a self-hosted, OpenAI-compatible
gateway in front of every provider (Anthropic, OpenAI, Google, local).
Every agent calls one endpoint; the router forwards, applies unified
auth, and logs cost/retries/timeouts. Good for: everything, by default
— the single point of cost control and model-swap. Not for:
editor-internal calls or flat-fee subscription tools. Cost: free to
run; provider passthrough only. Eval-native: emits per-call
cost/latency.
Local Inference (Ollama) ✅ — small open-weight models on our own
hardware (qwen2.5, llama3.1, nomic-embed-text). Good for: high-volume,
low-stakes classification/tagging/extraction/embeddings. Not for:
frontier judgment or long context. Cost: effectively free per call.
Layer 1 — Generic harnesses (the chassis)
Typed Agent Framework (Pydantic AI) ✅ — our default. Type-safe
Python; an agent is a normal object with typed output + tool schemas.
Validates output against a schema, retries on mismatch, tracing free,
ordinary Python control flow. Good for: structured single-shot work
(extract/classify/merge) and multi-step tool-using agents with DB
state. Not for: long autonomous coding (use a coding loop);
visual/YAML flows a non-engineer edits (use n8n); pure no-LLM plumbing.
Cost: free framework + model calls. Shape: runs
one-shot/bounded/continuous, single or supervisor. Memory: via
brain-api / agent_memory (not built-in). Eval: emits heartbeat; eval
hand-wired.
Agent/Chat Service Pattern (FastAPI + router + DB) ✅ — how we
host a typed agent as a real service: API endpoints backed by a DB,
models through the router, every call persisted. Good for: a tenant
"Second Brain" or any API fronting agents needing an audit trail. Not
for: cron-only batch (use a scheduled worker); pure frontend chat.
Cost: infra we already run.
Workflow Engine (n8n) ✅ — open-source visual node canvas with
hundreds of integrations. Good for: SaaS-to-SaaS data movement,
webhook fan-out across external APIs, flows a non-engineer inspects.
Not for: heavy reasoning (have it call an agent service), long
stateful conversations. Cost: free (self-hosted). Best as an
automation limb an agent calls.
Serverless Functions (Edge Functions) ✅ — small functions next to
our DBs, triggered on demand. Good for: webhook receivers,
DB-triggered logic, light routers, and callable tools an agent
invokes. Not for: long-running/heavy compute. Cost: fractional
cents/run; cold-start is the gotcha.
Workflow Engine, code-first (Archon engine) ✅ — third-party engine
executing YAML-defined workflows (shell/prompt/approval/loop steps),
with a web UI, resumable state, trigger API. (Vendor dependency — keep
the name; the workflows we build on it are our own "BC Pipelines.")
Good for: git-tracked multi-step pipelines where each step is a
script/command and you want observability + resumability. Not for:
non-engineer visual editing (n8n); interactive multi-turn agents (typed
framework). Shape: one-shot DAG / bounded; loops are explicit nodes.
Autonomous Coding (Claude Code on a managed VPS) ✅ — a CLI coding
agent under a flat-fee subscription on our server (bc-kb). Hand it a
goal + status file; it works autonomously for long stretches. Good
for: any task > ~15 min — bulk refactors, migrations, long
builds/backfills, hostile review passes. Not for: quick supervised
iteration; editor-side context. Cost: zero marginal cost on the
flat-fee plan — our biggest cost lever. Shape: verify-loop. Memory:
episodic (run logs) + procedural (CLAUDE.md). Eval: tests = the
verifier.
Complete-Agent SDK (Claude Agent SDK) 🧪 — official SDK giving a
model native computer access (shell/files/web/subagents). Good for:
projects where "give the agent a whole computer" is the point. Not
for: multi-tenant back-ends where model-cost flexibility matters —
don't fragment the stack. Cost: provider passthrough. We use its raw
single-call form in prod; the full package stays evaluated-not-standard.
Graph Memory (Cognee + Neo4j) ✅ — builds a semantic knowledge graph
as it ingests, combining vector search with graph traversal (researcher
→ lab → grant → trial → company). Good for: domains where conceptual
relationships are the value (academic-market intelligence). Not for: a
modest single-tenant corpus (a vector index in the tenant DB is
simpler). Cost: free software; cost is operational complexity.
Memory-native: BC's semantic/graph substrate, fed by the dream
cycle.
Width-Parallel Orchestrator (Claude Code Dynamic Workflows) 🧪 — a
script the coder writes that fans a task across dozens–hundreds of
sub-agents, then synthesizes. The script holds the loop + intermediate
results, so it scales wide. Good for: genuinely parallel jobs —
codebase-wide audits, 400–500-file migrations, cross-checked research.
Not for: single edits; mid-run human sign-off; anything cost-sensitive
without a small-slice probe — one run can burn half a monthly plan in
30 minutes. Shape: parallel-fan. Cap concurrency; route workers to
cheap models, synthesis to the strong one.
Others on the menu (briefly). IDE pair-programmer (Cascade) ✅ ·
Packaged skills ✅ · Long-form terminal coding (Devin) ✅ · Parallel
research subagents (Antigravity) ✅ · Graph orchestrator (LangGraph) 🧪
· handoff/voice SDK (OpenAI Agents SDK) 🧪 · drop-in chat stack
(OpenWebUI) ⚪.
Rejected (do not re-evaluate without a reason): CrewAI · Hermes
Agent · Letta/MemGPT · FlowiseAI · Agent Zero · Mem0/Zep
(memory-runtimes — we own the pattern) · Dify/Langflow · AutoGen/AG2 ·
claude-flow.
Layer 2 — House patterns (our body styles)
Not products — named conventions composed from Layers 0–1 so every build
reads the same way.
-
Brain-API pattern ✅ — canonical agent back-end: a service
fronting typed agents, routing through the router, persisting every
call. Each client inherits this shape. Memory-native (agent_memory +
/memory/dream). Eval: heartbeat. -
Heart-API pattern ✅ — the deterministic "pump": a no-LLM service
receiving heartbeats from every job/agent, summarizing health,
triggering workflows. Eval-native (the heartbeat_log / pulse layer). -
Scheduled Worker + Console pattern ✅ — a thin wrapper every cron
job runs under, capturing start/end/exit/logs to a central table.
Eval-native — run-logs is its whole point. Memory: episodic (run
rows). -
Signal-Desk pattern ✅ — reference shape for a recurring
multi-source intelligence desk: deterministic registry-based
ingestion → prefilter/dedup → a few bounded reasoning steps → human
approval gate → curated public feed. Behind Regen Radar.
Part 3 — Loops (the depth dimension)
Loop = depth: does it iterate, and until what? The single most
common failure mode is a "naive loop" — an agent that loops with no
verifier and no breaker, burning budget or spinning forever. Every loop
that isn't one-shot needs a checker and a stop-condition set.
(Landscapes: LOOP_VS_HARNESS_VS_SWARM.md,
LOOP_SWARM_ORCHESTRATION_LANDSCAPE.md, LOOP_ENGINEERING_PRIMER.md;
plain-language: LOOP_ENGINEERING_FOR_BRIAN.md.)
Loop-shape — controlled vocab:
| Tag | Meaning | BC examples |
|---|---|---|
| `one-shot` | no loop; single pass then return | a classify EF, a render, a news-pipeline node |
| `bounded-loop` | iterates to a fixed `max_turns` / budget | director task execution, Cascade turns |
| `verify-loop` | loops until a verifier/checker passes (maker→checker) | Grind (Ralph), `hostile_code_reviewer` |
| `optimizer-loop` | mutates + scores candidates against a metric (build-time) | DSPy optimization |
| `continuous` | always-on tick / never-terminating beat | Conductor ticks, brain research tick, dream-nightly |
The stop-condition set (mandatory whenever loop ≠ `one-shot`):
max_turns · per-run dollar cap · duplicate-call detection · wall-clock
timeout · an explicit "I'm stuck" exit. A loop without a codified
breaker set is not done.
Key insight: a verify-loop's checker IS its eval (Part 6). If you
can't write the checker, you don't have a verify-loop — you have a naive
loop. Define the pass/fail test first, then loop toward it.
Part 4 — Swarms (the width dimension)
Swarm = width: does it parallelize, and who coordinates? Width buys
throughput but multiplies cost and coordination overhead — reach for it
only when the work is genuinely parallel.
Swarm-shape — controlled vocab:
| Tag | Meaning | BC examples |
|---|---|---|
| `single` | one agent, no fan-out | most directors, a chatbot |
| `supervisor` | one coordinator splits + merges workers (orchestrator-worker) | Conductor → directors |
| `parallel-fan` | width-parallel bounded subagents merged | Dynamic Workflows, Antigravity subagents |
| `hierarchical` | supervisors of supervisors | (none live; reserved) |
| `peer/blackboard` | agents negotiate / share a state store, no center | (none live; reserved) |
How to read shape together: loop and swarm are independent.
Conductor is continuous · supervisor; Grind is verify-loop · single; a
Dynamic Workflow is bounded · parallel-fan. Most single-agent work stays
bounded · single on Pydantic AI. The cost trap lives in parallel-fan —
always run a small-slice probe before fanning wide. (More on agent
teams: AGENT_TEAMS_PRIMER.md.)
Part 5 — Memory (what it keeps, and how)
Knowledge ≠ memory. Knowledge (RAG) is the read path — look
something up at query time (we do this well). Memory is the write
path — decide what's worth keeping, update it when it changes, cross
out what's no longer true. The trap everyone falls into is bolting a
search index onto an agent and calling it memory; searching your past
isn't maintaining beliefs that change over time. (Landscape:
AGENT_MEMORY_ARCHITECTURES.md v1.1; plain-language: MEMORY_FOR_BRIAN.md;
where context lives: MCS_FILE_VS_DB.md.)
The four types (0..n held): working (context window only) · semantic
(durable facts) · episodic (run/conversation history) · procedural
(rules/CLAUDE.md/how-to-behave). BC's mix today is heavy procedural (our
whole MCS of rules + skills) + episodic (run logs) + a growing semantic
store (agent_memory).
Write-path — controlled vocab (increasing power/risk):
| Tag | Meaning |
|---|---|
| `none` | stateless; recomputes each run |
| `procedural-file` | behavior lives in files (`CLAUDE.md`/rules/skills) |
| `episodic-log` | run/tool-call history logged |
| `semantic-vector` | embed + retrieve durable facts |
| `agentic-self-edit` | the agent edits its own memory mid-loop (Letta-style) |
| `dream-cycle-propose` | BC pattern — propose-only candidates → overnight dream-cycle promote |
BC's rule: propose, then promote. An agent may suggest a memory,
but nothing becomes permanent until it's promoted/consolidated (the
dream cycle). We never let an agent silently self-write production
memory — same discipline as task-capture. Store: none · file · Hub
DB · Brain DB (agent_memory) · graph (Cognee-Neo4j) · client DB (route
per MCS_FILE_VS_DB.md).
When memory pays off: repeated interaction with the same people;
getting this time right depends on last time; re-deriving is costly.
When it's a liability: one-shot jobs; facts that change often (a
stale belief is worse than none); adding it because it sounds
impressive. Default to no memory; earn it. Store beliefs that can
change, not carved-in-stone facts. Multi-strategy retrieval + reranking
beats any single clever store — that's our open opportunity, and it's a
technique to adopt, not a vendor to buy (Mem0/Zep/Letta stay ❌).
Part 6 — Eval & observability (how we know it works)
Giving an agent memory is pointless if you can't tell whether it's doing
its job. Two different questions, different times (landscape:
EVAL_OBSERVABILITY_LANDSCAPE.md):
-
Evaluation = dev-time, pass/fail. Score output against criteria
before shipping. This is the verify-loop's checker (Part 3) — a
verify-loop needs an eval as its verifier. -
Observability = prod-time, monitoring. Traces, latency, cost, "did
it run / did it fail" after shipping.
Observability floor — controlled vocab:
| Tag | Meaning | BC examples |
|---|---|---|
| `none` | no measurement (throwaway only) | one-off prototype |
| `heartbeat` | liveness beat to Heart `heartbeat_log` / pulse | all crons + directors |
| `run-logs` | per-run status + output persisted | Workflow Console `wrap_run.py` |
| `telemetry` | per-invocation metrics | Made `skill_invocations` (BC gap) |
| `metric-eval` | scored against a metric/eval set | RAG eval (`rag_eval_set_v1.md`), DSPy metric |
| `tracing` | span-level trace (OTel + Langfuse/Phoenix) | (none live; adopt only at volume) |
BC floor = `heartbeat` + `run-logs` — every runtime,
non-negotiable and already cheap. Climb the ladder (telemetry →
metric-eval → tracing) only when a real need appears. If we ever adopt
tracing, prefer OTel-native + self-hostable (Langfuse/Phoenix) over
per-seat SaaS (LangSmith). Eval tools (DeepEval/Ragas/Promptfoo) are
methods we can run inside a verify-loop, not platforms we adopt. A
runtime with eval = `none` is not done.
Part 7 — BioCreative builds (the stack, proven)
Our own shipped systems — proof the menu produces real products. Each is
a build on menu harnesses, now shown with its full stack. (Operational
detail: AGENT_FLEET_REGISTRY.md; correlation: BC_FLEET_MATRIX.md.)
| Build | What it does | Harness | Shape (loop · swarm) | Memory | Eval |
|---|---|---|---|---|---|
| Cortex Conductor | spine director: proposes work, plans, synthesizes chat, hands off briefs | Typed → Brain-API | continuous · supervisor | semantic+episodic / dream-cycle | heartbeat+run-logs |
| Director shells (7) | role-specialized agents (Dev/Mktg/Sales/Fin/Legal/Ops/R&D), isolated tool surfaces | Typed → Brain-API | bounded · single/supervisor | varies / self-edit or vector | heartbeat |
| BC Pipelines | git-tracked multi-step workflows (MI reports, onboarding, landing builds) | Archon engine (YAML DAG) | one-shot/bounded · single | none/file | run-logs |
| Grind | self-iterating coding loop over a git worktree *(Ralph technique)* | Autonomous coding loop | verify-loop · single | episodic / episodic-log | run-logs (tests=verifier) |
| Signal Desk / Regen Radar | multi-source intelligence desk: ingest → bounded reasoning → human gate → feed | Scheduled Worker + bounded LLM | one-shot · single | none | heartbeat+run-logs |
None required inventing a new framework — each is an assembly chosen
by the method in Part 9.
Part 8 — Archetypes (recipes that compose harnesses)
Some agent kinds get built over and over. Capturing them as
archetypes means a new build starts from a decision, not a blank
page. A build is an instance of an archetype. (Catalog:
harnesses/archetypes/INDEX.md.)
-
Chatbot (documented, 7 variants) — a conversational surface over a
knowledge base; variants trade off back-end (agent service vs drop-in
vs serverless streaming vs director-backed) × surface (web app vs
ready-made UI) × memory (vector vs graph vs episodic). -
Research agent — deep-research pipeline, multi-model council, or
parallel subagents. -
Outreach agent — message generation + sequence provisioning into
outreach tools. -
Intelligence desk — the Signal-Desk shape.
-
Coding loop — the Grind / autonomous-coder variants.
Part 9 — The selection algorithm
Choosing the stack is deterministic enough to run as an algorithm. This
is the narrative form of the front door
(CAPABILITY_INTAKE_AND_BUILD_SHAPE.md) — the two must always agree.
The 60-second decision tree (harness)
1. One-shot structured output (extract / classify / merge)? → Typed
agent framework returning a schema.2. Long autonomous coding / infra work (> ~15 min)? → Autonomous
coding loop on the managed VPS (flat-fee).3. Multi-step workflow with branching + tools + state? → Typed agent
in the agent/chat service pattern.4. Visual / YAML workflow a non-engineer edits? → n8n OR Archon
engine (→ BC Pipelines).5. Memory IS the use case (long-lived, evolving relationships)? →
Graph memory OR a vector index in the tenant DB.6. Recurring multi-source monitor (news / trials / regs)? →
Signal-Desk pattern.7. Deterministic plumbing, no LLM? → Scheduled worker / serverless
function.8. Client-facing chat surface (a tenant Second Brain)? → Chatbot
archetype.9. Throwaway prototype? → IDE pair-programmer; promote if it
survives.
If nothing fits cleanly, you're inventing a new shape — stop and
document it before building.
The task → build procedure (the full stack, in order)
Mirrors the front-door steps 1–10:
-
Name the job in one sentence — trigger, input, output, who reads
it. -
Capability or runtime? — a reusable skill (→ SKILLS_INDEX) or a
deployed agent/service. -
Pick the shape — loop-shape (Part 3) × swarm-shape (Part 4). If
loop ≠ one-shot, write the stop-condition set now. -
Pick the harness — the tree above; prefer a tool-inside-an-agent
over a second full framework. -
Confirm skills — reuse from the library; note any to author.
-
Context — file / DB / hybrid; which DB (Hub operational · Brain
reasoning/memory · client tenant).
6b. Memory — types held? write-path? store? Default to none; earn
it; propose→promote if it learns.
- State + loop breakers — where state lives; breakers codified if
looping.
7b. Eval — heartbeat + run-logs floor minimum; climb only if needed.
-
MCS adds — skill doc / rule / workflow / canon update.
-
Trigger — scheduled · event · app · agent · on-demand ·
always-on. -
Register — matrix row (incl. shape/memory/eval) + registry +
skills index. Done = eval ≠ none, breakers set, memory
propose-only.
Job shape → recommended build (matrix)
| The job looks like… | Harness | Shape | Archetype |
|---|---|---|---|
| Pull structured fields from a document | Typed agent framework | one-shot · single | (single-agent) |
| Answer questions over a KB, in chat | Agent/chat service + vector/graph memory | bounded · single | Chatbot |
| Fan out to CRM/enrichment/outreach on a webhook | n8n, called as a tool | one-shot · single | (automation limb) |
| Assemble a multi-step deliverable, git-tracked | Archon → BC Pipeline | one-shot · single | (pipeline) |
| Watch many sources and curate a feed | Signal-Desk pattern | one-shot · single | Intelligence desk |
| Refactor / migrate a large codebase | Autonomous coding loop (+ width-parallel for scale) | verify-loop · single/parallel-fan | Coding loop |
| Coordinate several specialist agents | Typed → Brain-API | continuous · supervisor | Conductor / director |
| Tune a prompt against a metric | DSPy | optimizer-loop · single | (build-time) |
| Record a heartbeat / trigger a workflow, no LLM | Deterministic service / serverless | one-shot · single | (plumbing) |
The initial build is a lookup, not a research project.
Part 10 — The real work starts after spin-up
Because choosing the stack and standing up v1 is fast, the value shifts
downstream. For any non-trivial agent:
-
Refinement of judgment — prompt/schema iteration, few-shot,
evaluation loops so the agent is right, not just running. -
Cost & latency optimization — route each step to the cheapest
capable model, batch, cache, cap runs. (See cost-optimization rule +
llm_cost_live_reference.md.) -
Evaluation & guardrails (Part 6) — measure quality over time, add
human-approval gates where stakes are high, build the "did it fail /
did it drift" observability in from day one. -
Integration into the fleet — the compounding value. A lone agent
is useful; an agent reachable by the Conductor, emitting heartbeats,
reading/writing shared memory, handing work to other agents becomes
part of a system.
The discipline: spin up fast, then invest in the refinement,
optimization, memory hygiene, evaluation, and interconnection that turn
a working agent into a durable one. The harness is the chassis; the
craft is everything you tune after it drives.
Appendix A — Stack cheat-sheet
| Dimension | Quick pick |
|---|---|
| Harness | Structured/multi-step → Pydantic AI · long coding → autonomous loop · SaaS fan-out → n8n · git pipeline → Archon/BC Pipelines · fast glue → Edge Function · rich relationships → Cognee graph |
| Loop | none needed → `one-shot` · fixed budget → `bounded` · until-correct → `verify` · tune-to-metric → `optimizer` · always-on beat → `continuous` (breakers mandatory unless one-shot) |
| Swarm | one agent → `single` · split+merge → `supervisor` · massively parallel → `parallel-fan` (probe cost first) |
| Memory | default → `none` · behavior → `procedural-file` · history → `episodic-log` · facts → `semantic-vector` · learns over time → `dream-cycle-propose` (never silent self-write) |
| Eval | floor → `heartbeat` + `run-logs` · metric exists → `metric-eval` · debugging at volume → `tracing` (OTel, self-host) |
Appendix B — Glossary
-
Harness / Archetype / Build — a thing you build on / a recipe
composing harnesses / a shipped instance. -
Layer 0/1/2 — model substrate / generic harnesses / house
patterns. -
Shape — loop-depth × swarm-width (a dimension, not a layer).
-
Loop-shape — one-shot / bounded / verify / optimizer / continuous.
-
Swarm-shape — single / supervisor / parallel-fan / hierarchical /
peer. -
Stop-condition set — max_turns · \$cap · dup-detection ·
wall-clock · "I'm stuck" exit. -
Knowledge vs memory — read path (RAG) vs write path
(keep/update/expire beliefs). -
Memory types / write-path — working/semantic/episodic/procedural ·
none→procedural-file→episodic-log→semantic-vector→agentic-self-edit→dream-cycle-propose. -
Dream cycle — BC's propose-only → overnight-promote memory write
path. -
Eval vs observability — dev-time pass/fail (verify-loop's checker)
vs prod-time monitoring. -
Observability floor — heartbeat + run-logs (BC minimum).
-
MCS — Modular Context System; the nervous system tracking every
build. -
BC Pipelines / Grind — our workflows on the Archon engine / our
coding loop (Ralph technique). -
HITL — human-in-the-loop approval gate.
Appendix C — Internal doc map (the SoT wiring)
This guide teaches; these surfaces govern. When they disagree, they
win.
| Need | Doc |
|---|---|
| Front door (build anything) | `_executive/CAPABILITY_INTAKE_AND_BUILD_SHAPE.md` |
| The model (vocab + registration) | `_executive/AGENT_HARNESS_SKILL_STANDARD.md` (§Shape / §Memory / §Eval) |
| Correlation SoT (agent × harness × skill × shape × memory × eval × trigger × host) | `_executive/BC_FLEET_MATRIX.md` |
| Deployment SoT (containers, health, wiring) | `_executive/AGENT_FLEET_REGISTRY.md` |
| Harness menu (patterns + loop/swarm + memory/eval capability) | `departments/agent-brain/harnesses/INDEX.md` · `_CLASSIFICATION.md` · `archetypes/INDEX.md` |
| Loops/swarms (landscape + how-to) | `LOOP_VS_HARNESS_VS_SWARM.md` · `LOOP_SWARM_ORCHESTRATION_LANDSCAPE.md` · `LOOP_ENGINEERING_PRIMER.md` |
| Memory (landscape) | `AGENT_MEMORY_ARCHITECTURES.md` (v1.1) · `MCS_FILE_VS_DB.md` |
| Eval/observability (landscape) | `EVAL_OBSERVABILITY_LANDSCAPE.md` |
| Plain-language (for Brian) | `LOOP_ENGINEERING_FOR_BRIAN.md` · `MEMORY_FOR_BRIAN.md` |
| Naming | `NAMING_GLOSSARY.md` |
| Cost discipline | `.windsurf/rules/cost-optimization.md` · `llm_cost_live_reference.md` |
Prepared by BioCreative Strategies — internal engineering reference.
The living engineer-facing catalog (harness menu) and the
client-inheritable standard stay authoritative for build decisions; this
guide is the narrative that ties the stack together.
Microsoft Foundry & Agents
Download .docx02 — Microsoft Foundry + Agents
Deep-research synthesis on Microsoft Foundry + agents. Azure AI features move monthly — GA/preview tags are point-in-time (2026-06-17).
Triggers: "microsoft foundry", "azure ai foundry", "foundry agent service", "entra agent id", "model router", "foundry iq".
Naming (kill the confusion)
Azure AI Studio (2023) → Azure AI Foundry (2024) → Microsoft Foundry (the "Azure" prefix dropped Jan 1 2026). Stale docs will use all three. It's "an AI app and agent factory."
GA vs preview (2026) — read this before promising timelines
- GA: Foundry Agent Service (GA March 16 2026, Responses API runtime, OpenAI-wire-compatible) · end-to-end VNet/private networking · Foundry Evaluations & observability · Microsoft Agent Framework SDKs v2 (Python/JS/Java) · Model Router · Foundry IQ.
- Preview: Agent memory · Agent-to-Agent (A2A) orchestration · Agent Routines (scheduled agents) · Voice Live · custom evaluators/red-teaming.
- Hard deprecation: Azure OpenAI Assistants API retires Aug 26 2026 → build only on Foundry Agent Service. (Audit any prototype for Assistants API use.)
Core components
Model Catalog (11,000+ models) · Agent Service (runtime) · Foundry Tools (unified endpoint incl. MCP client support) · Foundry IQ (managed agentic RAG, the evolution of Azure AI Search) · Foundry Control Plane (governance/observability). Entra Agent ID gives each agent its own non-human identity under Zero Trust (huge for BW governance).
How common AI-app building blocks map to Foundry
| Building block | Foundry primitive | Note |
|---|---|---|
| Multi-provider model proxy | Model Router | One endpoint across providers; +$0.14/M input tokens infra fee |
| Agent framework | Microsoft Agent Framework / LangGraph on Hosted Agents | Gains Entra ID + tracing |
| RAG over Postgres + pgvector | Foundry IQ | Managed agentic RAG (or keep pgvector on the database) |
| Agent memory | Foundry Agent Memory | preview — don't depend on yet |
| Workflow automation (e.g. n8n) | Azure Logic Apps | 1,400+ connectors |
| Multi-step agent DAG | Foundry multi-agent workflows | Native orchestration |
| Sub-agent / multi-agent orchestration | Connected Agents / A2A | preview — orchestrate via LangGraph for GA reliability |
Recommendation
Hybrid: use Foundry for infrastructure, identity (Entra Agent ID), VNet, observability, and the Model Router — but write agent logic in a portable framework (LangGraph) on Foundry Hosted Agents. Maximizes Azure's enterprise features while limiting control-plane lock-in. Do NOT build mission-critical multi-agent flows on the A2A preview yet.
Cost drivers
Model tokens + Model Router fee ($0.14/M in) + Hosted Agents compute (scale-to-zero) + Foundry Memory + Foundry IQ indexing + observability storage. For CARR (daily briefs, transcription) the Model Router routing routine work to mini/Haiku-tier models is the main lever.
Why this matters for CARR/BW
This is the surface BW's incoming "AI architects" will standardize on. Pairs cleanly with the data tier (01_data_layer.md): once the data is in Azure (+ optional pgvector or Foundry IQ), CARR's daily-brief/content agents can run on Foundry with enterprise identity + audit.