An Agent Barn integration connects an Agent to an external service. A complete integration does more than collect a token: it validates the credential shape, encrypts it, materializes the correct runtime artifacts, teaches the Agent how to use the capability, exposes a safe configuration experience, and proves the entire path with tests.
This guide describes the contribution path for a new external provider. It does not cover adding Slack, Telegram, Discord, or another conversation transport. Those are communication platforms with separate lifecycle and runtime contracts.
- Audience
- Integration developers and maintainers
- Repository
agent-barn- Namespaces
- Existing Python and deployment namespaces continue to use
agent-farmwhere already established.
What you will accomplish
By the end of this guide, you will be able to:
- Classify the provider before changing code
- Define and validate its credential payload
- Preserve encrypted-storage and read-response guarantees
- Add a safe, read-only credential validator
- Materialize credentials into the correct Agent runtime
- Add an aai-cli Skill and Agent policy when applicable
- Expose the provider in every relevant web app surface
- Decide whether the provider supports Shared Credentials
- Preserve existing encrypted records when changing a schema
- Test storage, authorization, runtime generation, UI behavior, and secret safety
Before you begin
You should have:
- A working Agent Barn development environment
- Docker available for the API integration-test environment
- Non-production credentials for the external provider
- Documentation for the provider’s authentication and permission model
- A confirmed runtime access path
- A clear list of operations the Agent should be allowed to perform
Read these guides first:
- Understand the system architecture
- Develop against the API
- Develop the web app
- Work with Skills
- Manage Agent credentials
- Test a change
Before implementing an aai-cli-backed provider, confirm that the required command group is supported by the pinned aai-cli version. An Agent Barn profile cannot provide operations that the installed CLI does not implement.
Understand the integration boundary
Agent Barn has several related concepts that must remain separate.
External provider integration
Gives an Agent tools for an external service
GitHub, Jira, Confluence, Pipedrive
Communication platform
Carries messages between users and the Agent
Slack, Telegram, Discord
Agent Secret
Provider credential owned by one Agent
A per-Agent GitHub token
Shared Credential
Organization-owned credential attachable to Agents
A reusable Jira service account
Deployment secret
Configures the Agent Barn installation
Encryption keys and platform service credentials
Skill
Teaches an Agent how and when to use available tools
The built-in GitHub aai-cli Skill
Live validator
Checks whether a stored credential still works
A read-only provider identity request
Choose the runtime shape
Current integrations follow three main runtime shapes.
| Shape | Use when | Current pattern |
|---|---|---|
| aai-cli profile | The pinned aai-cli exposes the provider’s operations | GitHub, Jira, Confluence, Bitbucket, Zoho Mail, Slack data access, Pipedrive |
| Dedicated CLI | The provider requires an independent tool and credential store | Google Workspace through gog |
| Runtime capability | The runtime consumes provider configuration directly | Firecrawl web search and fetch |
These paths are intentionally different. Do not force a dedicated CLI or runtime-native provider through the aai-cli profile contract.
Keep provider access separate from chat delivery
Slack demonstrates why classification matters:
- Slack can be the Agent’s communication platform
- Slack can also provide data-access operations through an aai-cli Skill
- The aai-cli credential is derived from the configured Slack bot token when a remaining Skill requires it
- The user does not submit a second manual Slack integration credential
A new provider should only receive similar special handling when its configuration is genuinely derived from another authoritative source.
Plan the provider contract
Write down the provider contract before editing the enum. At minimum, decide:
- The stable provider ID
- The display label
- The authentication method
- Required and optional credential fields
- Which fields are secret
- Which fields affect endpoint or resource selection
- Whether credentials are entered manually or obtained through OAuth
- Whether the provider can be used as a Shared Credential
- Whether the provider uses aai-cli, a dedicated CLI, or runtime-native configuration
- The minimum safe operation for live validation
- The expected identity shown after validation
- Required and recommended provider permissions
- Whether missing permissions are fatal or should produce a warning
- Which Skill files teach the Agent the command surface
- How existing encrypted payloads will remain readable after future changes
Use a stable, lowercase provider value such as acme. This value crosses API, storage, runtime, Skill metadata, and UI boundaries. Treat it as a persisted identifier, not a display string.
Add the credential model
The authoritative provider contract lives in api/domains/agents/models.py. A normal provider addition updates four related definitions:
SecretProviderA SecretContent subclassPROVIDER_CONTENT_MODELSPROVIDER_DISPLAY_NAMES
class SecretProvider(str, enum.Enum):
# Existing providers...
ACME = "acme"
class AcmeContent(SecretContent):
api_token: str = Field(min_length=1)
base_url: str = Field(min_length=1)
PROVIDER_CONTENT_MODELS: dict[SecretProvider, type[SecretContent]] = {
# Existing mappings...
SecretProvider.ACME: AcmeContent,
}
PROVIDER_DISPLAY_NAMES: dict[SecretProvider, str] = {
# Existing labels...
SecretProvider.ACME: "Acme credential",
}All provider content models inherit:
class SecretContent(PydanticBaseModel):
model_config = ConfigDict(extra="forbid")This means unknown fields are rejected. Keep that behavior. Silently accepting misspelled or unused credential fields can create credentials that appear configured but cannot work.
Model only what runtime materialization needs
Credential content should contain the minimum data required to configure the runtime.
Good candidates
- Access token or API token
- Refresh token
- Account email
- Tenant, workspace, or organization identifier
- Base URL
- Repository or resource selection
- OAuth client ID and secret when the token is bound to a user-supplied client
Do not include
- Display-only UI state
- Validation results
- Last-used timestamps
- Plaintext duplicates of another field
- Provider API responses
- Permission lists that can be derived safely
- Agent policy prose
Validate on both write and read
Agent Barn calls validate_content() before encryption and calls it again after decryption.
def validate_content(provider: SecretProvider, raw: dict) -> SecretContent:
return PROVIDER_CONTENT_MODELS[provider].model_validate(raw)Revalidation after decryption prevents malformed stored content from reaching runtime builders. It also means every schema change must account for existing encrypted payloads.
Preserve credential security
Normal Agent Secret persistence already handles:
- Provider-specific schema validation
- Fernet encryption
- One credential per provider per Agent
- Server-defined display labels
- Manual credential upsert
- Explicit provider removal
- Shared Credential references
- Secret lifecycle Domain Events
- Secret-management authorization
Do not add a provider-specific plaintext column unless the architecture genuinely requires one.
A manual credential is sent through the normal Agent create or update contract:
{
"secrets": [
{
"provider": "acme",
"content": {
"api_token": "example-non-production-token",
"base_url": "https://api.example.invalid"
}
}
]
}The API response exposes metadata, not credential content:
{
"provider": "acme",
"secret_name": "Acme credential",
"shared_credential_id": null,
"shared_credential_name": null
}Preserve these invariants
- An Agent has at most one Agent Secret per provider
- Duplicate providers in one request are rejected
- A provider cannot be updated and removed in the same request
- A provider uses either a manual Agent Secret or a Shared Credential attachment, not both
- Credential content is encrypted before persistence
- Read APIs never return decrypted content
- Domain Events never include credential content
- Logs, validation errors, and exception messages never contain tokens
- Secret mutation requires the effective agent.secret.manage Permission
- Inaccessible Agents remain concealed according to the Agent Access contract
- Integration changes to a running Agent follow the existing stopped-Agent configuration rules
Add live validation
Live validation answers a narrow question: does this stored credential currently authenticate with sufficient access?
Add a provider-specific validator under api/infrastructure/integration_validators/. Use a safe, read-only request — prefer an identity, account, metadata, or list endpoint that cannot mutate provider state.
import httpx
from api.domains.agents.models import AcmeContent
from api.infrastructure.integration_validators.result import (
IntegrationValidationResult,
)
_TIMEOUT = 10
def validate_acme(content: AcmeContent) -> IntegrationValidationResult:
try:
response = httpx.get(
f"{content.base_url.rstrip('/')}/v1/me",
headers={"Authorization": f"Bearer {content.api_token}"},
timeout=_TIMEOUT,
)
except Exception as exc:
return IntegrationValidationResult(
valid=False,
error=f"Could not reach Acme: {exc}",
)
if response.status_code == 401:
return IntegrationValidationResult(
valid=False,
error="Token is invalid or expired",
)
if response.status_code == 403:
return IntegrationValidationResult(
valid=False,
error="Credential does not have account access",
)
if response.status_code != 200:
return IntegrationValidationResult(
valid=False,
error=f"Acme returned unexpected status {response.status_code}",
)
identity = response.json().get("email") or response.json().get("name")
return IntegrationValidationResult(valid=True, identity=identity)Register it in api/infrastructure/integration_validators/__init__.py:
PROVIDER_VALIDATORS: dict[SecretProvider, Any] = {
# Existing validators...
SecretProvider.ACME: validate_acme,
}The shared result contract maps to three user-facing states.
| Result | API status value | Meaning |
|---|---|---|
valid=True, no missing scopes | valid | Authentication and expected access succeeded |
valid=True, missing scopes present | warning | Authentication succeeded, but useful permissions are absent |
valid=False | invalid | Authentication, identity, or required access failed |
Use missing_scopes for access that is useful but not required to establish that the credential is real. Return an invalid result when the Agent cannot perform the integration’s essential operations.
Validation must:
- Avoid writes
- Use a bounded timeout
- Handle provider-specific error semantics
- Avoid returning raw provider response bodies
- Avoid including credentials in errors
- Return a useful identity when safely available
- Test network errors and unexpected statuses
- Remain non-persisting
Agent validation uses:
POST /api/v1/organizations/{organization_id}/agents/{agent_id}/integrations/{provider}/validateIt requires agent.secret.manage. The service decrypts the stored manual or Shared Credential only for the validation call.
Materialize the integration at runtime
Storing a credential does not make it usable. Agent startup must convert the decrypted provider content into runtime artifacts. At startup, Agent Service:
- Loads the Agent’s credential rows
- Resolves Shared Credential references
- Decrypts and revalidates provider content
- Builds provider configuration and secret-store setup
- Adds sensitive values to the Kubernetes Secret
- Adds secret-free setup and configuration to the ConfigMap
- Mounts eligible Skills
- Appends provider context and policy to the rendered Agent files
- Creates the selected Hermes or OpenClaw resources
aai-cli-backed providers
For aai-cli, update api/domains/agents/aai_cli_artifacts.py. An illustrative provider normally needs:
provider_secrets_map["acme"] = [
("acme.api_token", "api_token"),
]
PROFILE_SLUGS[SecretProvider.ACME] = "acme-work"It also needs a profile builder:
def _acme_block(content: AcmeContent) -> str:
return (
f"[profiles.{PROFILE_SLUGS[SecretProvider.ACME]}]\n"
'provider = "acme"\n'
'auth_type = "bearer_token"\n'
f"base_url = {_q(content.base_url)}\n"
'token_secret = "acme.api_token"\n'
)
_PROFILE_BUILDERS[SecretProvider.ACME] = _acme_blockAlso update the provider label and capability summary used in Agent context:
_INTEGRATION_LABELS[SecretProvider.ACME] = "Acme"
_INTEGRATION_CAPABILITIES[SecretProvider.ACME] = (
"projects, records, comments, and attachments"
)For an aai-cli provider, verify all of these surfaces:
- Secret-store name mapping
- Temporary setup environment
- config.toml profile
- Canonical --profile slug
- Configured-integrations context
- Always-loaded Agent policy
- Capability summary
- Built-in Skill files
- Hermes startup
- OpenClaw startup
Sensitive values belong in the Kubernetes Secret. ConfigMap content, Skill files, TOOLS.md, and AGENTS.md must remain secret-free.
Dedicated CLI providers
If the provider uses a separate CLI, follow the dedicated Google Workspace pattern rather than pretending it is an aai-cli profile. A dedicated CLI integration may require:
- Its own environment builder
- Its own setup script
- Its own on-disk credential home
- A separate policy block
- Base-image installation and version pinning
- Startup hooks in both runtime images
- Builder parameters for both Hermes and OpenClaw
- Focused artifact and image-contract tests
Document whether CLI state is persistent or rebuilt on every start. The encrypted database record should remain authoritative unless the architecture explicitly establishes another source of truth.
Runtime-native providers
For a runtime-native capability, add the provider to the specific runtime configuration path. The Firecrawl pattern demonstrates:
- A platform-level default can configure every Agent
- A per-Agent credential can override that default
- Hermes and OpenClaw require different generated configuration
- Sensitive values remain in the Kubernetes Secret
- Removing the Agent credential restores the platform default on the next start
Do not add a fake aai-cli profile for a provider that is actually consumed by runtime configuration.
Add the provider Skill
An integration needs instructions as well as credentials. Without a Skill or always-loaded policy, the Agent may not know:
- That the tool is available
- Which profile to use
- Which command hierarchy is valid
- What operations are supported
- What an error response means
- Which actions are read-only or mutating
- How to return files or attachments
For an aai-cli provider, add a module such as api/domains/agents/aai_cli_skills/acme.py, exporting one or more files:
ACME_SKILLS: list[dict[str, str]] = [
{
"skill_file_path": "aai-cli/acme_skill.md",
"skill_content": """\
# aai-cli Acme Skill
## Credentials are already configured
Use the configured profile. Do not ask the user for tokens.
## Required profile
Every command requires `--profile acme-work`.
## Command shape
```text
aai-cli --profile acme-work acme <resource> <verb>
```
Document the real resources, verbs, flags, responses, and errors here.
""",
},
]Then import it and register the built-in Skill in api/domains/agents/aai_cli_skills/__init__.py:
{
"name": "Acme",
"required_providers": [SecretProvider.ACME],
"files": ACME_SKILLS,
"entry_path": ACME_SKILLS[0]["skill_file_path"].removeprefix(
AAI_CLI_ROOT_DIR + "/"
),
"tools_pointer": (
"\nFor Acme, use the aai-cli tool. "
"See ./skills/aai-cli/acme_skill.md\n"
),
}The API startup seeder creates the global built-in Skill when it is absent. When shipped file content changes, it publishes a new Skill version only if the files differ from the latest stored version.
Configured provider credentials implicitly mount matching aai-cli Skills at Agent start. A built-in with no required providers is not automatically mounted.
Add the web app configuration
The provider catalog for Agent configuration lives in ui/src/features/agents/integrations.ts. Add a provider definition with fields matching the backend content model.
{
id: "acme",
label: "Acme",
scopeNote:
"Use a token with read access and only the write permissions the Agent requires.",
fields: [
{
key: "apiToken",
label: "API token",
type: "secret",
required: true,
placeholder: "acme_…",
},
{
key: "baseUrl",
label: "Base URL",
type: "text",
required: true,
placeholder: "https://api.example.invalid",
},
],
}The provider ID must exactly match the backend SecretProvider value. Enum values are not converted.
Web app field keys use camel case. The shared API client decamelizes request keys:
apiToken → api_token
baseUrl → base_urlSupported integration field types currently include:
textsecretrepo-listradiocheckbox-list
IntegrationFields is shared by the hiring flow, Agent Skills configuration, and Agent credential settings. Extend the shared component when the provider needs a genuinely new reusable field behavior.
When adding fields:
- Mark required fields accurately
- Provide scope guidance
- Keep token inputs masked
- Add useful placeholders without showing real credentials
- Convert string-backed radio values to booleans when the API model expects booleans
- Update incomplete-integration checks
- Preserve field behavior while the Agent is running
- Verify create, edit, remove, and reconnect flows
- Update fixtures and Playwright expectations together
OAuth is not enabled by a label
Decide whether credentials can be shared
Shared Credentials are Organization-scoped credentials managed independently of an Agent. They are appropriate when:
- The credential is entered manually
- Several Agents should use the same service account
- Rotation should occur once at the Organization level
- Attaching the credential does not require a user-specific OAuth callback
- The provider’s authorization model permits shared service credentials
To enable a provider, add it to SHARED_CREDENTIAL_ALLOWED_PROVIDERS in api/domains/shared_credentials/models.py.
Preserve these rules:
- Only Organization owners and admins create, update, validate, or delete Shared Credentials
- Organization members can list credential briefs and attach them to accessible Agents
- Names are unique within an Organization
- Multiple credentials for the same provider are allowed
- A credential cannot be read across Organizations
- An Agent cannot have a manual and shared credential for the same provider
- Deletion is blocked while a non-deleted Agent references the credential
- Read responses never contain credential content
- Runtime startup resolves and decrypts the referenced Shared Credential
- Live validation uses the same provider validator when one is registered
Add Shared Credential UI coverage if the provider becomes eligible.
Add OAuth when required
OAuth providers need more than a credential schema. A secure OAuth implementation should define:
- The provider-specific authorization endpoint
- A typed, signed, short-lived state value
- The authenticated user or Organization context carried by that state
- Requested service and permission choices
- Callback handling
- Server-side code exchange
- Returned identity and granted scopes
- Assembly of the normal provider credential payload
- Reconnection and token-rotation behavior
- Tests for invalid, expired, tampered, and mismatched state
Use the Google Workspace implementation as a concrete repository pattern:
api/domains/integrations/google_oauth/routes.py
ui/src/features/agents/hooks/use-google-oauth.tsDo not copy Google-specific assumptions into another provider. Scope derivation, consent behavior, refresh-token rules, callback parameters, and client ownership vary by service.
Handle stored-data compatibility
Provider content is stored as encrypted JSON and validated again after decryption. Changing a model can make every existing credential for that provider unreadable.
Safe additive changes
- Adding an optional field with a default
- Adding a field that can be derived after decryption
- Adding a compatibility validator that converts a legacy shape
- Preserving an old field while transitioning runtime behavior
Potentially breaking changes
- Renaming a required field
- Removing a field from a model with extra="forbid"
- Changing a string into a list without coercion
- Changing the provider value
- Requiring a new secret for existing records
- Moving a provider between runtime systems without a transition
A compatibility conversion can follow the existing repository-list pattern:
@model_validator(mode="before")
@classmethod
def upgrade_legacy_shape(cls, data):
if isinstance(data, dict) and "old_field" in data and "new_field" not in data:
upgraded = dict(data)
upgraded["new_field"] = upgraded.pop("old_field")
return upgraded
return dataagent_secret.provider and shared_credential.provider are stored as strings, so adding an enum member alone does not normally require a PostgreSQL enum migration. A migration is still required when the database shape, persisted data, constraints, or existing Skill metadata must change.
Provider removal requires an explicit cleanup plan. Remove or transform affected credential rows and Skill requirements before deleting the enum member.
Test the complete integration
Test the lowest layer that proves each contract, then cover the assembled behavior.
| Concern | Minimum coverage |
|---|---|
| Provider enum and display name | Unit test covering complete provider mappings |
| Credential shape | Valid, missing-field, extra-field, and default-value unit tests |
| Encrypted storage | Encrypt/decrypt round trip and plaintext-absence assertion |
| Legacy compatibility | Decrypt an old encrypted shape into the new model |
| Agent create | Integration test showing the provider in secret metadata |
| Agent update | Add, replace, and remove behavior |
| Duplicate provider | Request validation failure |
| Running Agent | Lifecycle conflict behavior |
| Authorization | agent.secret.manage, inaccessible Agent, and cross-Organization cases |
| Domain Events | Added, updated, and removed events contain no credential content |
| Live validation | Success, warning, invalid credential, network error, and unexpected response |
| aai-cli profile | Exact TOML, profile slug, secret names, setup environment, and policy |
| Dedicated CLI | Environment, setup script, policy, and image contract |
| Runtime-native provider | Hermes and OpenClaw generated configuration |
| Skill | Required provider, file paths, pointer, and command documentation |
| Automatic mounting | Configured provider mounts its built-in aai-cli Skill |
| Shared Credential | Role, tenant, attach, detach, validation, and deletion restriction |
| Web app | Hiring, credential settings, validation, removal, and Shared Credential selection |
| Documentation | Provider behavior and source map remain accurate |
Representative test locations include:
api/tests/unit/test_agent_secrets.py
api/tests/unit/test_integration_validators.py
api/tests/unit/test_aai_cli_artifacts.py
api/tests/unit/test_aai_cli_skills.py
api/tests/unit/test_gog_artifacts.py
api/tests/integration/test_agents.py
api/tests/integration/test_shared_credentials.py
api/tests/integration/test_agent_start_shared_credential.py
ui/tests/e2e/hire-dialog.spec.ts
ui/tests/e2e/agent-configuration-page.spec.ts
ui/tests/e2e/shared-credentials.spec.tsRun the checks for every touched area:
make check-api
make test-api
make lint-ui
make check-ui
make test-uiIf the integration changes Kubernetes resource generation or runtime images, also run the relevant Kubernetes and image-contract verification.
make test-api-k8sUse make coverage when you need to inspect API coverage in detail.
Verify the integration end to end
After automated tests pass, verify one complete non-production flow.
- Start Agent Barn locally.
- Sign in to a test Organization.
- Create or select a stopped Agent.
- Add the new provider credential.
- Confirm the API response contains provider metadata but no content.
- Run live validation.
- Confirm the expected identity and permission state.
- Start the Agent.
- Inspect generated runtime configuration without printing secret values.
- Confirm the provider Skill is mounted when applicable.
- Ask the Agent to perform a safe read operation.
- Confirm the Agent uses the documented CLI, profile, or runtime tool.
- Exercise one permitted mutation if the integration supports writes.
- Stop the Agent.
- Rotate or replace the credential.
- Restart and confirm the new credential is used.
- Remove the credential.
- Restart and confirm access is gone or the documented platform default applies.
- Review Agent activity and logs for errors or accidental credential exposure.
For a Shared Credential, repeat the flow with two Agents and confirm that rotation affects both after restart.
Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| Provider is rejected with 422 | Provider ID or credential fields do not match the backend schema | SecretProvider, content model, provider map, and UI key conversion |
| Provider appears configured but the Agent cannot use it | Runtime materialization is incomplete | Profile builder, environment, setup script, runtime config, and image dependency |
| aai-cli reports an unknown provider or command | The pinned aai-cli does not support the command surface | aai-cli version and the Skill’s documented commands |
| Agent asks the user for credentials | Provider context, policy, or Skill is missing | Tool context, Agent policy, Skill registration, and automatic mounting |
| Validate returns no-validator error | The provider was not registered in PROVIDER_VALIDATORS | Validator module and registry |
| Validation succeeds but operations fail | The validation probe does not cover essential permissions | Required scopes and safe resource probes |
| Validation reports a warning | Authentication works but recommended permissions are absent | missing_scopes and provider token configuration |
| Existing credentials fail after an upgrade | The content schema is not backward compatible | Defaults, compatibility validators, and stored-data migration plan |
| Credential appears in API output | Response DTO or serialization exposes internal content | Agent Secret read model and service mapping |
| Credential appears in logs or events | Secret-bearing values are being formatted or serialized | Exceptions, logging, Domain Event payloads, and test assertions |
| Provider is absent from the hiring flow | UI provider catalog was not updated | INTEGRATION_PROVIDERS and required-field checks |
| Shared Credential cannot be created | Provider is not eligible or not allowlisted | Authentication method and SHARED_CREDENTIAL_ALLOWED_PROVIDERS |
| Agent start fails only on one runtime | Only one runtime builder or startup path was updated | Hermes and OpenClaw builders, scripts, and image contents |
| Configuration works until restart | Runtime state is being treated as authoritative | Startup reconstruction and encrypted credential source of truth |
| Removing a credential breaks a required Skill | The Skill still declares that provider requirement | Skill assignment and remaining-provider validation |
Completion checklist
Work through each card before opening the pull request.
-
Provider contract
-
Backend
-
Runtime
-
Skills and policy
-
Web app
-
Verification and documentation
Source map
| Concern | Authoritative source |
|---|---|
| Provider enum and credential content | api/domains/agents/models.py |
| Agent Secret persistence | api/domains/agents/service.py |
| Agent Secret queries and events | api/domains/agents/repository.py |
| Provider validation registry | api/infrastructure/integration_validators/__init__.py |
| Provider validators | api/infrastructure/integration_validators/ |
| aai-cli runtime artifacts | api/domains/agents/aai_cli_artifacts.py |
| Google Workspace runtime artifacts | api/domains/agents/gog_artifacts.py |
| Runtime orchestration | api/domains/agents/service.py |
| Hermes Kubernetes builders | api/domains/agents/builders/hermes.py |
| OpenClaw Kubernetes builders | api/domains/agents/builders/openclaw.py |
| Built-in aai-cli Skills | api/domains/agents/aai_cli_skills/ |
| Built-in Skill seeding | api/domains/skills/skill_seeder.py |
| Shared Credential eligibility | api/domains/shared_credentials/models.py |
| Shared Credential lifecycle | api/domains/shared_credentials/ |
| Google OAuth reference flow | api/domains/integrations/google_oauth/routes.py |
| UI provider definitions | ui/src/features/agents/integrations.ts |
| Shared credential inputs | ui/src/features/agents/components/integration-fields.tsx |
| Agent response schemas | ui/src/features/agents/schemas.ts |
| Agent create and update mutations | ui/src/features/agents/hooks/use-create-agent.ts, use-update-agent.ts |
| Live validation hook | ui/src/features/agents/hooks/use-validate-integration.ts |
| Integration feature contract | docs/features/integrations.md |
| Backend conventions | docs/guidelines/code.md |
| Web app conventions | docs/guidelines/webapp.md |
| Test conventions | docs/guidelines/testing.md |