Skip to content

Archaeological Specs: Using AI Agents Safely on a Salesforce Org You Didn't Build

Before an AI agent changes an org you didn't build, map what it does now: retrieve metadata, trace dependencies, write an as-is spec and pin it with tests.

Series · AI-Native Development · Part 3 of 6

For Salesforce developers, admins and architects about to let an AI agent change an org with years of history. This is the "start from an existing system" step that comes before Intent → Spec → Test.

TL;DR

  • Most changes land in orgs nobody fully understands. Before an agent changes anything, find out what the area does today, including side effects nobody documented.
  • Give the agent files, not the org. A person retrieves the metadata into a folder; the agent reads that. If it needs data, use a read-only integration user in a sandbox.
  • Trace what touches the area. The Dependency API helps, but it is still Beta and has gaps, so pair it with a text search of the retrieved metadata.
  • Write an as-is spec in order of execution, then pin it with characterization tests, which record what the org does now, right or wrong.
  • People decide what is intended. Only then write the new spec and must-nevers.

What You'll Learn

  • How to give an agent enough of an org to analyse, without letting it change anything
  • How to retrieve one area and trace what touches it, and where the Dependency API falls short
  • How to write an as-is spec in Salesforce's order of execution
  • How to pin current behaviour with characterization tests before anything changes

The Problem

Spec-driven development assumes you know what the system does and are adding something new. Most Salesforce work is not like that. A "small" change to Case or Opportunity lands on top of triggers, record-triggered Flows, validation rules, workflow, Process Builder leftovers, sharing rules and integrations, several written by people who have left.

Point an AI agent at that and ask for a change, and it will happily make one. It will not know that a Flow three hops away sets the field it just changed, or that an integration overwrites it every night. The agent is fast; the archaeology is what keeps it safe.

Common Questions This Article Answers:

  • How do I find out what my org actually does before I change it?
  • Can I let an AI agent analyse my org without giving it access to change anything?
  • How do I make sure a change does not break behaviour nobody wrote down?

Quick Answer

Retrieve the area you are about to change into a local folder with the sf CLI, and let the agent read the files, not the org. Trace everything that touches the area with the Dependency API (Beta, with gaps) plus a text search of the retrieved metadata. Have the agent write an as-is spec that lists each automation in the order Salesforce runs them, then write characterization tests: Apex tests and Flow tests that record what the org does today. A person reviews the as-is spec and marks each behaviour as intended, a bug or unknown. Only after that do you write the new spec and must-nevers, and the characterization tests tell you exactly which existing behaviour your change alters.

A few words you will see

Term What it means here
As-is spec A written description of what the org does now, bugs included, before anyone changes it
Characterization test A test that records current behaviour so any change to it shows up. Michael Feathers coined the term in Working Effectively with Legacy Code
Dependency API The Tooling API object MetadataComponentDependency, which lists which components reference which
Order of execution The fixed sequence in which Salesforce runs flows, triggers, validation and other automation when a record is saved
Must-never A line the business says can never be crossed, such as "a guest user must never read Contact records"

Step 1: Give the Agent Files, Not the Org

The safest agent for archaeology has no org access at all. A person retrieves the metadata, and the agent reads the files. Nothing it does can change the org.

If the agent also needs to look at data (how many records hit a branch of a Flow, which values a picklist really holds), give it a read-only identity in a sandbox, never production:

  • The Salesforce Integration user licence is built for API-only, system-to-system access. Enterprise Edition and above include 5 seats; Developer Edition includes 1.
  • It comes with the Minimum Access - API Only Integrations profile, so the user cannot log in to the UI. Grant read access through a permission set using the Salesforce API Integration permission set licence, and nothing else.

Step 2: Retrieve the Area

Pull only what you need. Generate a manifest from the org, narrowed to the types around the area, then retrieve with it:

sf project generate manifest --from-org my-sandbox --metadata CustomObject,ApexClass,ApexTrigger,Flow,SharingRules
sf project retrieve start --manifest package.xml --target-org my-sandbox

Commit the result to a branch of its own. It is the baseline the as-is spec describes.

Step 3: Trace What Touches It

Two sources, because neither is complete on its own.

The Dependency API. MetadataComponentDependency lists which components reference which. Know its limits before you rely on it:

  • It is still Beta in the Winter '27 Tooling API guide: "for evaluation purposes only".
  • A Tooling API query returns at most 2,000 records; Bulk API 2.0 raises that to 100,000.
  • Reports are not included in Tooling API queries; use Bulk API 2.0 for those.
  • Community reports describe further gaps, such as some Flow and custom label references. These are not documented, so treat them as warnings, not facts.

A text search of the retrieved files. Search the metadata for the object and field API names. It finds references the Dependency API cannot, including dynamic SOQL strings in Apex. For a single custom field, Setup's Where is this used? button is a quick extra check.

Neither source sees managed package code you cannot read, or integrations that write to the org from outside. List both as known unknowns, and ask whoever owns the integrations what they touch.

Step 4: Write the As-Is Spec in Order of Execution

Have the agent describe each automation on the object in the order Salesforce runs them. Salesforce documents the sequence in Triggers and Order of Execution. In short, for each save:

  1. before-save flows, then before triggers;
  2. validation rules and duplicate rules;
  3. the save, then after triggers;
  4. assignment, auto-response and workflow rules, then escalation rules;
  5. after-save flows, then roll-up summaries and criteria-based sharing;
  6. commit, then post-commit work such as email and asynchronous Apex.

An entry in the as-is spec might read:

## Case: on create (as-is, 10 October)
- before-save flow "Case Defaults": sets Origin to Web when blank
- trigger CaseTrigger (before insert): copies Account.Tier__c to Case.Tier__c
- validation rule Require_Contact: blocks a save without ContactId
- after-save flow "Escalate VIP": Priority = High when Tier__c = 'Gold'   <- unknown: intended?
- integration "Support Sync" (external): overwrites Priority nightly       <- known unknown

Ordering matters because it explains surprises: a value set by a before-save flow can be overwritten by a before trigger a moment later.

Step 5: Pin Current Behaviour With Characterization Tests

A characterization test records what the org does now, whether or not it is right. Name it for the behaviour, not the intent, so nobody mistakes it for a requirement:

@IsTest
private class CaseOnCreateCharacterizationTest {
    @IsTest
    static void currentlyGoldAccountCasesBecomeHighPriority() {
        Account acc = new Account(Name = 'Gold customer', Tier__c = 'Gold');
        insert acc;
        Contact con = new Contact(LastName = 'Customer', AccountId = acc.Id);
        insert con;

        Case c = new Case(AccountId = acc.Id, ContactId = con.Id, Subject = 'Help');
        insert c;

        c = [SELECT Priority, Origin FROM Case WHERE Id = :c.Id];
        Assert.areEqual('High', c.Priority, 'Current behaviour: Gold accounts get High priority');
        Assert.areEqual('Web', c.Origin, 'Current behaviour: blank Origin defaults to Web');
    }
}

Two things to know when writing them:

  • Apex tests cannot see existing org data unless they use @IsTest(SeeAllData=true), so create the records the test needs. User, Profile and RecordType records are visible without it.
  • Flow tests cover record-triggered, autolaunched and Data Cloud-triggered flows. They deploy as FlowTest metadata and run with sf flow run test, so a Flow-heavy area can be pinned without Apex.

When your change lands, any characterization test that fails shows you exactly which existing behaviour moved. Either that was the point, and you update the test, or it was not, and you have caught a regression before production.

Step 6: Decide What Is Intended

The as-is spec describes behaviour, bugs included. Never treat it as a requirement until a person has confirmed it. Go through it with the people who own the process and mark each line:

  • Intended: keep it, and consider a must-never if breaking it would be serious.
  • Bug: fix it deliberately, as its own change.
  • Unknown: find out before you build on it.

Only now write the to-be spec and the must-nevers for the change, as described in Intent → Spec → Test. The characterization tests stay in the suite as a safety net.

Frequently Asked Questions

Q: Why not let the agent query the org directly?

A: Because reading files is enough for most of the analysis, and an agent with org access can be steered into doing more than read. Retrieve the metadata as a person and give the agent the folder. If it needs data, use a read-only integration user in a sandbox, never production.

Q: Can I rely on the Dependency API?

A: Not on its own. It is still Beta, capped at 2,000 records per Tooling API query, leaves out reports in those queries, and users report further gaps. Pair it with a text search of the retrieved metadata, and treat managed packages and external integrations as known unknowns.

Q: Are characterization tests the same as normal unit tests?

A: They are written the same way but mean something different. A unit test says what the code should do. A characterization test says what it does today, so a change in behaviour is visible. Some will turn out to describe bugs, which is useful to know.

Q: How much of the org should I map?

A: Only the area you are about to change and what touches it: the object, its automation, its sharing and the integrations that write to it. Mapping a whole org is a project of its own and goes stale quickly.

Key Takeaways

  • Find out what the area does before an agent changes it. The agent is fast; the archaeology keeps it safe.
  • Give the agent metadata files, not org access. Use a read-only integration user in a sandbox only when data is needed.
  • Trace with two sources: the Dependency API (Beta, with gaps) and a text search of the retrieved files.
  • Write the as-is spec in order of execution and pin it with characterization tests, then let people decide what is intended.

What's Next?

Recommended Reading:

Action Items:

  1. Pick the next area you are changing and retrieve just its metadata into a branch.
  2. Have an agent write the as-is spec from the files, in order of execution, and mark the unknowns.
  3. Write one characterization test per behaviour someone would miss if it changed.

Resources & References

Responses

Checking your session.

Loading responses.