Building and managing agents
The Foundry tools that create, edit, start, stop, and delete agents, the tool that starts an ephemeral coding agent, the tools that handle one Jira issue including a read-only resident investigation, and how a person grants them from the Flow Intelligence Desktop App.
People and agents build agents with the same Foundry: The service that owns agents: each one's definition, the tools it is granted, and whether it is running. People and agents build new agents through the same Foundry tools. tools. An agent
running on a harness calls them as tools; a person calls them from a conversation in
the Flow Intelligence Desktop App. Both paths use the shared operations in
services/foundry/internal/agents, then call the Foundry API over HTTP for authorization and persistence. They differ in
caller identity, grant policy, and how the caller reviews a proposed change.
Diagram source
flowchart LR
Person[person] -->|"describe, review, approve"| Desktop[Flow Intelligence Desktop App]
Desktop -->|"MCP"| Gateway[Foundry MCP gateway]
Builder[builder agent] -->|"tool calls"| Harness[Agent Harness V1]
Gateway --> Tools[Foundry tools]
Harness --> Tools
Tools -->|"HTTP"| API[foundry-api]The tools
| Tool | Inputs | Result |
|---|---|---|
list_agents | Optional page, pageSize | Each agent's id, name, slug, run type, and state |
get_agent | Exactly one of id or slug | The complete definition and current state |
list_tool_catalog | Optional harness_id, family, page, pageSize | Tool names, families, and descriptions, without schemas |
list_harnesses | None | Each harness's tools, skill support, and features |
create_agent | name; optional definition fields and idempotency_key | A new agent, stopped |
update_agent | id; the fields to change and optional idempotency_key | The updated agent |
start_agent | id; optional idempotency_key | The running agent |
stop_agent | id; optional idempotency_key | The stopped agent, or a completed swarm member |
delete_agent | id; optional idempotency_key | The deleted agent, with its runtime stopped and its swarm members removed |
spawn_coding_agent | id, task, remote_url; optional base_branch, name, path_prefixes, ttl_seconds, approval (person or self), harness_id, idempotency_key | A running ephemeral coding agent. id is the persistent parent. The pull request targets the repository default branch. An ephemeral agent approves its own plan either way |
create_skill | name, category, markdown; optional description, version, idempotency_key | A skill in the library. markdown is stored as SKILL.md under category (productivity, engineering, security, testing, data_analytics, or other). version defaults to 1 |
read_jira_issue | id, issue_key | The named issue's summary, status, and description. issue_key must be the one key in this agent's instructions |
investigate_jira_issue | id, issue_key | A short comment for recommend_jira_issue, from read-only identity, linkage, lease, Grafana, Carson, and Salto checks. The comment states what was found and the next step a person should take. The tool logs in to Carson, reads the delegation user_id, and checks Salto permitted doors when that id is present. Nothing is written. No email means the comment says so and DeepSky is not called. Missing Carson credentials do not stop the other reads |
recommend_jira_issue | id, issue_key, text | One Jira comment with the steps a person should take, and a stored attempt with no coding agent. A second call returns that attempt |
claim_jira_issue | id, issue_key, repo (deepsky or platform) | One ephemeral coding agent. deepsky is the legacy flow app on GitHub, limited to the deepsky tree, and the pull request targets development. platform is this repository, and the pull request targets the remote default branch. A second call returns the existing attempt |
report_jira_attempts | id | One Jira comment for this parent's open coding attempt once the run has finished. A pull request URL is the comment. A finished run with no pull request says so |
list_move_ins | id, property_code; optional from, to (YYYY-MM-DD, at most 31 days apart) | The people moving in at one Yardi property, read through DeepSky's legacy PMS connector: Yardi codes, unit, move-in date, Yardi status, leaseholder flag, relationship, and for email and phone whether each is present, valid, and shared with someone else. No name, email, or phone number. Defaults to three days back and seven ahead. Cached for 30 minutes |
check_resident_access | id, property_code, tenant_code, person_code | One incoming resident's app access state from the same read-only DeepSky, Carson, and Salto checks as investigate_jira_issue, looked up by Yardi codes. Facts, not a verdict. When the email resolves to more than one DeepSky identity, identity_fids lists them and survivor_fid names the one to keep in a merge: the one linked to Carson, else the only one bound to a lease, else empty so a person decides. No name, email, or phone number. Cached for an hour per person |
list_unit_mapping_gaps | id | The Yardi units at Fort Lauderdale, Miami East, Miami West, and Brickell that DeepSky's legacy PMS connector could not map to a DeepSky space in the last day, read from its lease sync logs. A unit with no mapping keeps every lease on it out of DeepSky, so the resident has no key in the app. For each: the tenant codes it blocks, how often it failed, exact_match (the one DeepSky space with exactly the unit's name, for example SL-3202C and Unit 3202C) when there is one, and candidates (empty bedrooms in the same apartment) for a person to choose from. Coworking units are left out. Cached for 30 minutes |
apply_unit_mapping | id, property_code, unit_code | Maps one unit from list_unit_mapping_gaps to the DeepSky space with exactly its name, then resyncs the lease of each tenant it blocks. foundry-api re-reads DeepSky first and answers 409 when no single space has exactly the unit's name, or when the connector has not logged the unit as unmapped in the last day. A repeated call within a day returns the first result without writing again. At most 10 a day |
list_missing_yardi_emails | id | The Yardi tenants at Fort Lauderdale, Miami East, Miami West, and Brickell whose lease DeepSky's legacy PMS connector could not sync in the last day because Yardi has no email for them, read from its lease sync logs. Such a lease never reaches DeepSky. For each: property_code, tenant_code, building, and how often it failed. Cached for 30 minutes |
fill_yardi_emails_from_carson | id, property_code, tenant_code | Fills the missing emails on one lease from list_missing_yardi_emails, then resyncs the lease when any was written. The connector does the match and the write: for each person on the lease with no email, the one Carson resident on the same lease with the same first and last name gives the email, and only an empty email is written. Returns each person's outcome by Yardi person code; the email never reaches Foundry. foundry-api answers 409 for a tenant the connector has not logged in the last day, or a property its writeback is off for. A repeated call within a day returns the first result without calling the connector again. At most 30 a day. With apply_unit_mapping, the only tools here that write outside Foundry |
A tool does one thing and decides nothing. Whether a new agent duplicates one that exists, what it is for, and whether a change needs approval are up to the caller: a builder agent's instructions, or the person reviewing a proposal.
The tools act on persistent agents, except spawn_coding_agent, which creates
an ephemeral coding agent and does not edit it afterward. A swarm member can be
read, stopped, and started again, but it is never created, edited, or deleted on
its own, and list_agents leaves it out. Both lists return five items a page by default;
list_agents allows up to ten and list_tool_catalog up to twenty.
create_agent and update_agent take max_turns, the model steps one Agent Harness
V1 evaluation may take. The minimum is 10. Omit it on create to use 10. A higher
value is kept.
create_agent makes persistent agents only. Ephemeral agents come from
spawn_coding_agent or a swarm spawn, so that the persistent agent which
started one is also what spins it down.
Agents building agents
Any harness that runs the foundry family can give these tools to an agent. Agent
Harness V1 is the first. To make a builder, open an agent in the intelligence-app
Foundry workspace and pick the tools it needs from the Foundry group in its Tools
field. What to build, and how to check whether an agent already exists, goes in its
instructions or a skill.
Each grant adds to what the builder can do:
| Grant | What the builder can do |
|---|---|
list_agents, get_agent, list_tool_catalog, list_harnesses | See the agents its owner can see, and what each harness can run |
create_agent | Draft new agents, which stay stopped until someone starts them |
update_agent | Change the instructions, models, tools, and skills of any persistent agent its owner can edit, itself included |
start_agent, stop_agent, delete_agent | Start and stop agents its owner can edit, and remove agents its owner owns, without waiting for a person |
spawn_coding_agent | Start an ephemeral coding agent for one GitHub or Azure DevOps repository |
create_skill | File a markdown skill in the library under a category |
read_jira_issue, investigate_jira_issue, recommend_jira_issue, claim_jira_issue, report_jira_attempts | Handle the one Jira issue named in this agent's instructions |
list_move_ins, check_resident_access | Read incoming residents at the Yardi and Carson buildings, by Yardi codes only |
list_unit_mapping_gaps, apply_unit_mapping | Find Yardi units the PMS connector cannot place, and map the ones with an exact DeepSky name |
list_missing_yardi_emails, fill_yardi_emails_from_carson | Find Yardi tenants the PMS connector skips for a missing email, and fill them from Carson |
A builder acts as its owner, the person in its owner_fid. It reaches the agents that person
owns and the ones shared with them, at the role they hold, and gets 404 for every other agent.
get_agent and list_agents report that role as caller_role, so a builder can tell what it
may change before it tries. Anyone with editor access to a builder can direct it, and it still
acts as its owner. See
docs/decisions/0078-foundry-agents-have-members.md.
Only a person who can edit the agent can grant create_agent, update_agent, start_agent,
delete_agent, spawn_coding_agent, create_skill, read_jira_issue,
investigate_jira_issue, recommend_jira_issue, claim_jira_issue, report_jira_attempts,
list_move_ins, check_resident_access, list_unit_mapping_gaps, apply_unit_mapping,
list_missing_yardi_emails, or fill_yardi_emails_from_carson.
A builder cannot submit those in the
tools list of a create or update.
Omitting tools on an update preserves existing grants, including those;
other fields can still be edited.
spawn_coding_agent does not copy the parent. The child takes the task as its
instructions, branches from the repository default branch unless base_branch
is set, and opens a pull request against that default branch. When the task
asks for the change on another branch too, such as develop, the child can
copy the same commits there with cherry_pick_branch and open a second pull
request with open_pull_request and base. The task decides the order. It
does not merge.
The parent picks the child's harness with harness_id, which must be a harness
that declares the repo_scope feature; list_harnesses shows which ones do.
Omitting harness_id uses Coding Harness V1. get_agent on the child includes
last_run once the harness records the run; last_run.pull_request_urls lists
every pull request the run opened, the default branch one first.
last_run.change gives the lines added and removed, the files changed, and the
verification commands the run ran, described in
Operations. See
docs/decisions/0060-agents-spawn-coding-agents.md.
One Jira issue
The Jira parent is created by hand after deploy. It is a persistent always-on
agent on Agent Harness V1. A person grants investigate_jira_issue and
recommend_jira_issue, and pastes instructions that name one issue key.
An unfinished issue in project TRI is eligible, whoever it is assigned to.
Finished issues stay out. The parent is not granted spawn_coding_agent.
claim_jira_issue and report_jira_attempts remain for a coding run on
the same one-issue rule. A resident investigation does not use them.
Replace TRI-123 with the issue to try. To try the next issue, edit the
instructions to that key.
The only issue is TRI-123.
Call investigate_jira_issue for TRI-123.
If stop_reason is already_commented, stop.
Otherwise call recommend_jira_issue for TRI-123 with text set to the comment field only. Do not add a cause the tool did not return.
The tool runs the read-only Carson lookup and the Salto door check. Do not write. Do not post to Slack. Do not change the Jira status.
Do not call claim_jira_issue. Stop after TRI-123.deepsky clones [email protected]:life-in-flow/flow.git, edits only the
deepsky tree, and opens the pull request against development. platform
clones this repository and opens the pull request against the remote default
branch. A recommendation is one comment and no coding agent. A stored attempt
is returned again without a second comment or a second agent. A claim is
refused while a coding run is still open. See
docs/decisions/0095-one-jira-issue-at-a-time.md.
An agent built by another agent records agent:<creator-id> in created_by, shown as
Creator in the console, and inherits the creator's owner. It also starts with a copy of the
creator's editors and viewers. Later changes to either agent's members do not carry over.
People building agents
People already create and edit agents in the intelligence-app Foundry workspace. The
Flow Intelligence Desktop App adds building one in conversation: a person describes
the agent they want, reviews an editable proposal, revises it, and approves the create
or update. The app runs that conversation and calls the tools through the Foundry MCP
gateway, signed in as that person. The model proposes creates and updates with
propose_agent_change; the app calls create_agent or update_agent after approval.
A person can edit the fields directly or reply in chat to request changes. The
action dropdown offers Create agent or Create and start, and the
corresponding save actions for updates. Changing a running agent's run type or
harness requires Save and restart, which stops, updates, then starts it.
Foundry validates the definition and applies it.
Because the caller is a person, an approved change can grant any tool, including the
ones a builder agent cannot, on an agent that person can edit. The gateway adds
list_skills, get_skill, and list_models, so a proposal names only skills and models
that exist. The new agent records the person's FID in created_by and makes them its owner.
Running the gateway locally is covered in services/foundry/cmd/mcp/README.md.
What a definition holds
create_agent and update_agent accept name, slug, instructions, run_type,
model, memory_model, harness_id, project_id, practice, conversation_id,
tools, and skills.
On create, only name is required. Omitted fields default to always_on, Agent
Harness V1, auto for both models, no extra tools or skills, and a slug proposed
from the name. An update requires id and the fields to change. A skill follows its latest version unless version_id pins
one.
Creating an agent never starts it. Stop a running agent before changing its run_type
or harness_id.
Updates keep what you leave out
update_agent reads the latest definition and applies only the fields the call
supplies, so a caller can change one field without restating the rest. This merge
belongs to the shared tool operations, not the raw PUT endpoint. It preserves
omitted fields from the latest read, but does not lock out concurrent edits
between that read and the write.
| Input | Effect |
|---|---|
| Field omitted | Keeps its current value |
A tools or skills list | Replaces the current list, so it must include every entry to keep |
Empty instructions | Clears the instructions |
Empty tools or skills | Removes all of them |
project_id, practice, or conversation_id set to null | Clears that association |
Retrying a call
Every write takes an optional idempotency_key. Reuse one only to retry the same call
with the same input; leave it out and a new one is generated. A dropped connection, a
502, or a 504 is retried once with the same key. When start_agent or stop_agent
returns an error, read the agent before trying again with a new key.
The endpoints behind each tool are in the API reference. See also docs/decisions/0057-shared-agent-building-capabilities.md.
Move-in readiness
A move-in readiness agent checks, each morning, whether the people moving in at the Yardi and Carson buildings will be able to use the resident app. It emails each property what the property team needs to do, and emails the OneFlow on-call engineer what only engineering can do. The reasons are in docs/decisions/0108-move-in-readiness-is-an-agent-with-connector-tools.md.
Foundry holds only the two read tools. The checklist is the agent's instructions below, so changing a rule is an edit to the agent, not a deploy. The tools take and return Yardi codes and DeepSky and Carson identifiers. The harness writes every tool call's arguments into the agent's workspace conversation, so a name, email, or phone number never goes through a tool call, and the emails carry codes only. Staff look a resident up in Yardi by tenant code; Carson searches by name or email.
The fixes follow the front desk's Carson and Yardi troubleshooting guide: the front desk and managers correct Yardi data, confirm details with the resident in writing, and run Sync Residents from Yardi; deleting or adding a profile or unit in Carson, forcing a sync from the admin panel, and merging DeepSky identities are engineering only.
The agent is created by hand after deploy: a persistent always-on agent on
Agent Harness V1. A person grants list_move_ins, check_resident_access,
and send_email, and pastes the instructions. remember and recall are
always granted on that harness. Replace the recipients with real ones.
The agent re-evaluates every few seconds. Three things keep that from
repeating work: the agent records each property as done for the day with
remember, the tools cache their reads, and each email carries an
idempotency_key, so a repeated send_email returns the first send instead
of sending again.
Only Fort Lauderdale and Wynwood use Salto door locks. Wynwood is not on Carson, so the agent checks door keys at Fort Lauderdale only; elsewhere the front desk issues and tests a key fob.
When a resident's email resolves to more than one DeepSky identity,
check_resident_access returns identity_fids and, when one holds the Carson
link or the only lease, survivor_fid. The agent puts both in the engineering
email so the on-call engineer can run the merge-duplicate-identities skill
straight away. The agent does not merge: the identity service's merge routes
are reachable only inside the DeepSky cluster, and a person confirms every
merge.
A Carson user is sometimes deleted and created again with a new ID. The
identity then still points at the old Carson user, and the sync never fixes
it, because it only picks up Carson users created in the last 12 hours. When
the email resolves to one identity whose Carson link names a different Carson
user than the one Carson has now, check_resident_access returns relink
with the identity and both Carson user IDs. The agent puts these in the
engineering email. The on-call engineer replays the Carson identity migration
for the new Carson user, which points the existing identity at it. The agent
does not replay it: that is a write to DeepSky's production NATS.
Once each New York day, after 06:30, check whether the people moving in will be able to use the resident app, and email what needs doing.
Properties and the property team to email (only Fort Lauderdale has Salto door locks):
- r2108 Flow Fort Lauderdale: [email protected]
- r2109 Flow Miami East: [email protected]
- r2501 Flow Miami West (Flow House): [email protected]
- r2502 Flow Brickell: [email protected]
OneFlow on-call engineer to email: [email protected]
If it is before 06:30 in New York, stop.
Call recall with "move-in readiness <YYYY-MM-DD>" for today's date. A property with a memory "move-in readiness <YYYY-MM-DD> <property code> done" is finished for today; skip it. If every property is finished, stop.
For each property that is not finished:
1. Call list_move_ins with the property code. If it times out, go to the next property and come back next evaluation; the read keeps running.
2. Skip anyone whose relationship is Guarantor. Guarantors do not get app access.
3. For everyone else, call check_resident_access with the property code, tenant_code, and person_code.
4. Decide what needs doing for each person, in this order. "Due" means move_in_date is today, already past, or within 2 days. A roommate is anyone whose person_code starts with r, or whose is_lessee is false.
a. Yardi status: if yardi_status is Future and the person is due, the move-in is not completed in Yardi. Property team: a manager moves them from Future to Current in Yardi, then runs Sync Residents from Yardi in Carson. Until then, do not report the Carson account, DeepSky identity, link, or lease for this person; they cannot exist yet. If yardi_status is Future and the person is not due, nothing is needed yet.
b. Email and phone: if email.valid or phone.valid is false, property team: confirm the right email and phone with the resident in writing (Zendesk or a Zoom Phone text) and update their profile in Yardi.
c. Shared contact: if email.shared_with or phone.shared_with lists anyone, property team: confirm in writing whose email and number is whose, and give each person their own in Yardi. A shared email or phone stops the second profile from reaching Carson.
d. Relationship: if relationship is blank or Other, property team: set Roommate or Spouse in Yardi.
e. If email_usable is false, stop here for this person.
f. Carson account: if facts.carson_lookup is found, it is in place. If not_found and the person is due and Current, property team: after b to d are correct, run Sync Residents from Yardi and wait 30 minutes; if still missing, engineering checks for a leftover profile in Carson. If not_tenant, engineering: Carson already has an account with this email that is not a resident here, usually a leftover visitor profile; engineering removes or fixes it, then the property team syncs. If failed or carson_configured is false, say the Carson check could not run.
g. DeepSky identity, only when the Carson account is in place. If facts.denied, or auth_status or contact_status is not 200, say the identity check could not run. If identity_fids lists more than one, engineering: merge the duplicate identities with the merge-duplicate-identities skill, giving identity_fids, survivor_fid if set (otherwise "survivor to decide"), survivor_reason, and carson_user_id. If identity_fids is empty and the person is due, engineering: run the full Carson identity migration, because the sync only picks up Carson accounts created in the last 12 hours. If auth_fids is empty but contact_fids is not, engineering: re-index the identity's sign-in methods.
h. Carson link, only when the Carson account and exactly one identity are in place. If facts.relink is set, the identity still points at a Carson user that was replaced. Engineering: re-link identity relink.fid from Carson user relink.from_carson_user_id to relink.to_carson_user_id by replaying the Carson identity migration for the new Carson user (the replay in the investigate-resident skill). This is not a merge. Otherwise, if legacy_state is anything other than match and imported is false, engineering: re-run the Carson identity migration for this person.
i. Lease, only for someone who is not a roommate, only when exactly one identity is in place, and only on or after move_in_date. If lease_problem is set, engineering, with that text. If no lease has identity_bound true and yardi_status is Current, engineering: check the lease in Waves and the PMS connector lease sync.
j. Unit mapping: if unit_mapping_missing, engineering: upload the unit mapping to the PMS connector.
k. Unit door key, only at r2108 Flow Fort Lauderdale, and only when the Carson account is in place. Fort Lauderdale is the only building here with Salto locks; at every other property leave the door out entirely, and the front desk issues and tests a key fob as usual. At Fort Lauderdale, if salto_doors is 0, say the Salto check returned no doors and the onsite team should confirm the resident's access. If unit_door is false or unit_key is not valid and the person is due, onsite team: if unit_key is absent, the unit door is not on the resident's access; check their unit in Carson and that the unit's lock is set up. If unit_key is null or expired, ask the lock vendor to reissue the Salto key.
5. Write the property email if any person at the property has a property team item. Subject: Move-in app access: <number of people> need attention at <property name> (<Mon D>). One block per person: "Unit <unit_code>, moving in <move_in_date> (Yardi tenant <tenant_code>, person <person_code>, Yardi status <yardi_status>)", then each property team item with its fix. If three or more people at the property are due and still Future, start the email with one line saying the move-ins are not completed in Yardi for those units and list them, instead of repeating the same fix under each person. End with: "Look the resident up in Yardi by tenant code. After a Yardi fix, run Sync Residents from Yardi in Carson and allow 30 minutes. Deleting or adding anyone in Carson is engineering only. This check only reads; nothing was changed in Yardi, Carson, or DeepSky."
6. Write the engineering email if any person has an engineering item. Subject: Move-in app access: engineering at <property name> (<Mon D>). One block per person: the same first line, then "Carson user <carson_user_id or none>", then each engineering item with the identifiers it names. For a merge, write the identities, the survivor, and the reason exactly as the tool returned them. For a re-link, write the identity and both Carson user IDs exactly as the tool returned them.
7. You do not have names, emails, or phone numbers. Do not ask for them or guess them.
8. Call send_email once per recipient for each email, with idempotency_key "move-in-readiness:<YYYY-MM-DD>:<property code>:<property or engineering>:<recipient>". Send nothing when there is nothing to do.
9. Call remember with "move-in readiness <YYYY-MM-DD> <property code> done". Do this even when no email was needed.
Do not write to any other system. Do not post anywhere else.Unit mapping gaps
DeepSky's legacy PMS connector syncs each Yardi lease onto a DeepSky unit or bedroom, through a
mapping from the Yardi unit code to the DeepSky space. When a unit has no mapping, every lease on
it fails to sync and the connector logs "No unit mapping found" on every run. The resident's lease
never reaches DeepSky, so the app shows no key. A bedroom added in Yardi after the building was
set up in DeepSky is the usual cause, as with SL-3202C and SL-3202A at Fort Lauderdale.
list_unit_mapping_gaps reads those failures for the last day. When DeepSky has exactly one
space with the unit's name, apply_unit_mapping uploads the mapping and resyncs the tenants. When
it does not, the right space is a judgement: SL-3202A had no Unit 3202A, and the empty
bedroom was named Unit 3202D. The tool refuses those, and the agent emails engineering the
candidates and the exact commands, so a person who knows the building confirms the room first.
The agent is created by hand: a persistent always-on agent on Agent Harness V1. A person grants
list_unit_mapping_gaps, apply_unit_mapping, and send_email, and pastes the instructions.
Replace the recipient with a real one.
Once each New York day, after 07:00, fix the Yardi units DeepSky's PMS connector cannot place, and email engineering the ones a person has to decide.
Engineering to email: [email protected]
If it is before 07:00 in New York, stop.
Call recall with "unit mapping gaps <YYYY-MM-DD>" for today's date. If there is a memory "unit mapping gaps <YYYY-MM-DD> done", stop.
1. Call list_unit_mapping_gaps. If it times out, stop and try again next evaluation; the read keeps running.
2. For each gap that has exact_match, call apply_unit_mapping with its property_code and unit_code. Record what it returns. If it answers 409, treat the gap as one a person decides. If it answers 429, stop applying and treat the remaining exact matches as ones a person decides.
3. Write one email if anything was applied, any gap has no exact_match, or any apply failed. Subject: Unit mapping gaps (<Mon D>). Write every date as <Mon D>.
a. Applied: one line per mapping, "<building> <unit_code> -> <deepsky_space_name> (<deepsky_space_fid>), resynced tenants <resynced_tenants>". If resync_failures lists anyone, add "resync failed for <tenants>; trigger it again".
b. To decide: one block per gap without exact_match: "<building> <unit_code>, blocking tenants <tenant_codes>, <failures> failed syncs in the last day", then the note, then each candidate as "<name> (<kind>, <fid>, leases <leases>)". Then: "Confirm with the property team which room this is. Then upload the mapping (schemaType unit-mapping-v1, row <PROPERTY CODE IN CAPITALS>,<unit_code>,<chosen fid>) to POST /legacy-pms-connector/mappings/upload, and trigger POST /legacy-pms-connector/sync/trigger with entityTypes leases, propertyCode <property_code>, and tenantCode for each tenant." If a gap has no candidates, write that DeepSky has no matching space and the building model may need the room added.
c. Leave out any gap whose note says it is not a property this tool covers.
4. You do not have names, emails, or phone numbers. Do not ask for them or guess them.
5. Call send_email once to engineering with idempotency_key "unit-mapping-gaps:<YYYY-MM-DD>:oncall". Send nothing when there are no gaps.
6. Call remember with "unit mapping gaps <YYYY-MM-DD> done", even when nothing was sent.
Do not write to any other system. Do not post anywhere else.Missing Yardi emails
DeepSky's legacy PMS connector needs an email on each Yardi tenant to find the resident's DeepSky account. When Yardi has none, the lease sync skips the tenant before it looks, logs "Missing email", and the lease never reaches DeepSky. Most of these residents do have an email in Carson, because Carson imports them from Yardi and keeps the Yardi tenant code on each resident.
list_missing_yardi_emails reads those failures for the last day. fill_yardi_emails_from_carson
asks the connector to fill one lease. The connector finds, for each person with no email, the one
Carson resident on the same lease with exactly the same first and last name, and writes their
Carson email to Yardi. It writes only an empty email, never overwrites one, never retries, and
reads the person back afterwards. Anything less certain is left alone and returned as a reason.
Foundry only sees Yardi codes and outcomes; the email stays inside the connector. See
docs/decisions/0113-foundry-fills-missing-yardi-emails-from-carson.md.
The agent is created by hand: a persistent always-on agent on Agent Harness V1. A person grants
list_missing_yardi_emails, fill_yardi_emails_from_carson, and send_email, and pastes the
instructions. Replace the recipient with a real one.
Once each New York day, after 07:30, fill the missing Yardi emails that Carson can supply, and email the ones a person has to fix.
Recipient: [email protected]
If it is before 07:30 in New York, stop.
Call recall with "missing yardi emails <YYYY-MM-DD>" for today's date. If there is a memory "missing yardi emails <YYYY-MM-DD> done", stop.
1. Call list_missing_yardi_emails. If it times out, stop and try again next evaluation; the read keeps running.
2. For each tenant, call fill_yardi_emails_from_carson with its property_code and tenant_code. Record what it returns. If it answers 409, record the message as the reason. If it answers 429, stop and treat the remaining tenants as not tried today. If it answers 502 or 503, record "the connector or Carson could not be read" and go on.
3. Sort each person's outcome:
- Filled: APPLIED.
- Needs Yardi checked: EMAIL_NOT_SAVED (Yardi accepted the write but the email does not read back) or OTHER_FIELDS_CHANGED (the person's name or phone changed, or they are gone). These come first in the email.
- Property team: NO_CARSON_MATCH (no Carson resident on the lease has this name; add the email in Yardi), AMBIGUOUS_NAME (two people share the name; confirm who is who), CARSON_HAS_NO_EMAIL, CARSON_RESIDENT_CHANGED, EMAIL_USED_ON_LEASE (two people on the lease would share one email; get each their own).
- Nothing to do: EMAIL_ALREADY_SET, PERSON_NOT_FOUND.
4. Write one email only if something needs a person: any "needs Yardi checked" or "property team" outcome, any 409, 502, or 503, or a resync_failed. Do not send an email that only lists filled leases. Subject: Missing Yardi emails (<Mon D>). Write every date as <Mon D>.
a. Needs Yardi checked: one line each, "<building> tenant <tenant_code>, person <person_code>: <outcome>". Say someone should open the person in Yardi and check the email, name, and phone.
b. Property team: one line each, "<building> tenant <tenant_code>, person <person_code>: <what to do>".
c. Could not run: one line each with the reason.
d. If resync_failed is true for a lease, add "<building> tenant <tenant_code>: the email was filled but the lease resync failed; it syncs on the next run".
e. Last line: "Filled today: <count> people on <count> leases." Count the APPLIED outcomes.
5. You do not have names, emails, or phone numbers. Do not ask for them or guess them.
6. Call send_email once with idempotency_key "missing-yardi-emails:<YYYY-MM-DD>:oncall", only when step 4 says to.
7. Call remember with "missing yardi emails <YYYY-MM-DD> done", even when nothing was sent.
Do not write to any other system. Do not post anywhere else.