A concise map of the moving pieces: GL SDK / GLAIP, GLAIR Connectors, and CATAPA Digital Employee.
flowchart TB
subgraph GL["GL SDK / GLAIP"]
A["Agent runtime<br/>LLM calls, memory, streaming<br/>PII masking, tracing"]
CLI["aip CLI / TUI<br/>local terminal"]
SDK["glaip-sdk Python"]
end
subgraph CATAPA_SDK["CATAPA SDK"]
C["catapa<br/>Public CATAPA API"]
CP["catapa_private<br/>Internal CATAPA API"]
CORE["digital-employee-core<br/>DE abstraction layer"]
end
subgraph CONN_SDK["GL Connectors SDK / Tools"]
CS["gl_connectors_sdk<br/>Direct connector API"]
CT["gl_connectors_tools<br/>Skill factory + MCPs"]
end
subgraph CONN_HOST["GLAIR Connectors host"]
H1["connectors.glair.ai<br/>Public API"]
H2["connectors.gdplabs.id<br/>Internal runtime"]
CON["Connectors Console"]
end
subgraph DE["CATAPA Digital Employee"]
D["LangChain/LangGraph agents<br/>tools + MCPs"]
end
subgraph UI["User interfaces"]
GLC["GLChat web UI<br/>enterprise chatbot"]
CL["Claudia<br/>CATAPA-branded chat"]
end
SDK -->|wraps| A
CLI -->|uses| SDK
A -->|serves agents| GLC
A -->|serves agents| CL
CL -->|proxies to| GLAPI["GL API /message"]
GLC -->|uses| GLAPI
A -->|calls| C
A -->|calls| CP
A -->|forwards| CS
A -->|uses| CT
CORE -->|used by| D
CS -->|REST| H1
CS -->|REST| H2
CT -->|installs skills/MCPs| H2
A -->|gl_connectors_token| D
D -->|catapa SDK| C
D -->|catapa_private SDK| CP
D -->|sync connector methods| CS
H1 -->|OAuth callback| D
H2 -->|MCP server| D
CON -->|configure integrations| H1
CON -->|configure integrations| H2
Everything in the diagram is connected, but the arrow style matters:
- Solid arrows = direct code/API dependencies.
- Labeled arrows = token propagation or runtime wiring.
- GLAIP can talk to CATAPA data, GL Connectors API, and GL Connectors MCP servers.
- A Digital Employee reuses the same SDKs and connector runtime, but adds its own agent topology and CATAPA-specific guardrails.
GL SDK = GDP Labs Software Development Kit. GLAIP = the agent-interaction layer (SDK + runtime).
Key packages:
| Package | What it does |
|---|---|
glaip-sdk |
Agent runtime, Agent, run_agent, streaming, runtime_config, trace, MCP |
gl-connectors-sdk |
Direct connector API fluent interface |
gl-connectors-tools / gl-connectors-tools-binary |
Skill factory, install skills from GitHub |
catapa |
Public CATAPA API SDK |
catapa_private |
Internal/private CATAPA API SDK |
Common AIP patterns:
from glaip_sdk import Agent, MCP
agent = Agent(
name="github-agent",
instruction="Summarise PRs",
mcps=[MCP.from_native("github")],
mcp_configs={"github": {"allowed_tools": ["github_list_pull_requests"]}},
)Token propagation for connector auth:
client.run_agent(
agent_id="...",
message="...",
gl_connectors_token="<user-token>",
)gl_connectors_tokenis forwarded to/agents/{agent_id}/runand used for GL Connectors pre-checks.- During local
make runit may not reach custom tools; add env fallback (GL_CONNECTORS_TOKEN) in the tool.
GLAIR Connectors is an OAuth gateway and tool catalog for external services (Google, GitHub, Slack, SQL, etc.).
| Use when | Approach |
|---|---|
| One precise external operation | Direct API (gl_connectors_sdk) |
| Agent needs broad/uncertain access to a service | MCP server with allowed_tools |
| Repeatable workflow, formatting, rules | Agent Skill |
Base URLs:
https://connectors.glair.ai— common public APIhttps://connectors.gdplabs.id— internal/GL ecosystem runtime- Console:
https://connectors.glair.ai/console
Main endpoints:
POST /clients— create client (API key holder)GET /clients,PATCH /clients— list / updatePOST /users— create userPOST /auth/tokens— login, get user tokenDELETE /auth/tokens— logoutGET /users/meGET /connectors/{name}/auth-schema— auth config shapePOST /connectors/{name}/integrations— create integrationGET|POST|DELETE /connectors/{name}/integrations/{user_identifier}GET /connectors/{name}/success-authorize-callback— OAuth callbackGET /catalog/mcp— list MCP serversGET /catalog/tools— list native toolsGET /skills/list— curated skills registryGET /skills/openclaw— UGC skillsGET /api/skills— skill details listPOST /api/skills— register a new skillPOST /api/skills/tenants— attach skill to tenantGET /api/skills/tenants/{client_id}PUT|GET|DELETE /api/skills/{skill_id}
from gl_connectors_sdk.connector import GLConnectors
connector = GLConnectors(
api_base_url="https://connectors.glair.ai",
api_key=client_key,
)
response = (
connector.connect("google_drive")
.action("search_files")
.params({"query": "name contains 'wfo'"})
.token(user_token)
.run()
)https://connectors.gdplabs.id/<connector>/mcp
Examples:
- GitHub:
https://connectors.gdplabs.id/github/mcp - Google Mail:
https://connectors.gdplabs.id/google_mail/mcp - Slack:
https://connectors.gdplabs.id/slack/mcp
Auth: X-Api-Key header for client, Authorization: Bearer <user-token> for user. Optional X-Integration to pick a specific integration.
Always restrict tools:
mcp_configs={
"github": {"allowed_tools": ["github_list_pull_requests"]}
}A Digital Employee (DE) is an LLM-driven agent inside CATAPA that answers domain-specific requests and performs workflows.
- Single agent: one domain, few tools, no specialist boundary.
- Coordinator + sub-agents: multiple specialists, different tools per responsibility, coordinator routes work.
Default: prefer single agent unless multi-agent clearly reduces risk.
Source of truth repo: https://github.com/GDP-ADMIN/prompt-template/tree/main/project-wide/digital-employee
Steps:
- Prepare PRD.
- Read
de-build-de-catapa/README.mdandAGENTS.md. - Fill
<REPLACE: ...>placeholders in step prompts. step-1-scaffolding.shstep-2-architecture-specification.md- Review architecture (tools, MCPs, sub-agents, missing/unnecessary parts).
step-3-implementation-specification.md- Review spec (no invented endpoints, no invented query syntax, clear env vars, good errors).
step-4-implementation.md- Configure
.env. make run MESSAGE="...".step-5-validation-and-compliance.md.step-6-integration-test-verification.md(live tests when feasible).
prompt-template-single-agent-instructions.mdprompt-template-multi-agent-coordinator-instructions.mdprompt-template-multi-agent-subagent-instructions.md
These are instruction scaffolding, not runtime code.
Public import:
from catapa import CatapaPrivate/internal import:
from catapa_private import CatapaPrivateSDK version: minimum >=0.3.3; TMO pins 0.3.6. Pin to latest compatible per DE.
Use RSQL ?filter=:
?filter=companyId==zfrl;status==active
?filter=employeeId=in=(123,456)
?filter=createdAt=gt=2025-01-01
Operators: ==, =in=, =gt=, =ge=, =lt=, =le=, =contains=.
Combine with ; (AND) or , (OR within same field).
Legacy ?query= with : / > / < / , is deprecated and overloaded; avoid in new code.
Pagination: default size is small; use up to 50 per page from CATAPA-API OpenAPI spec. TMO uses internal RECAPITULATION_DETAIL_PAGE_SIZE = 400 for its own batching.
- Never guess CATAPA endpoints or query params.
- Enforce tenant isolation.
- No raw PII in logs/output.
- No
logger.info()on raw result objects. - Use HITL for risky writes.
- Register MCPs per-agent, not globally.
- Prefer built-in tools/MCPs over custom code.
- Keep business reasoning in prompts/instructions, not tool/client code.
| Need | Use |
|---|---|
| Single deterministic external call | gl_connectors_sdk API |
| Agent chooses among many service actions | MCP server with allowed_tools |
| Repeatable format/checklist/workflow | Agent Skill |
| CATAPA HRIS data | catapa / catapa_private SDK |
| Live CATAPA action | CATAPA SDK + correct tenant/user |
For Excel/PDF artifacts + email:
- Generate artifact, upload to object storage, get presigned URL.
- Call
google_mailsync method inside LangChain_run(async fails inside running event loop). - Build email body in tool with
_format_email_body()so markdown table/sections survive LLM collapse. - Use real presigned URL; never leave placeholder variables.
Use gogcli (gog gmail get/search) rather than browser automation. Faster and authoritative.
# Run a DE locally
make run MESSAGE="Run lateness frequency analysis for Finance last month"
# GL Connectors SDK install
uv add gl-connectors-sdk
# GL Connectors tools/skill install
uv add gl-connectors-tools-binary
# AIP SDK install
uv add glaip-sdk
# CATAPA SDK
uv add catapa catapa-private
# GitHub prompt-template source
gh api repos/GDP-ADMIN/prompt-template/contents/project-wide/digital-employee/de-build-de-catapa/README.mddigital-employee-core is the Python framework library every CATAPA DE is built on. It wraps glaip-sdk and adds DE-specific primitives.
uv add digital-employee-core
# or from CodeArtifact:
uv pip install --index-url https://aws:$CODEARTIFACT_AUTH_TOKEN@$CODEARTIFACT_REPOSITORY_ENDPOINT/simple/ digital-employee-corefrom digital_employee_core import (
DigitalEmployee,
DigitalEmployeeAgent,
DigitalEmployeeBuilder,
DigitalEmployeeConfig,
DigitalEmployeePipelineBuilder,
DigitalEmployeeState,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
DigitalEmployeeSupervisor,
ConfigTemplateLoader,
)
from digital_employee_core.constants import (
DEFAULT_MODEL_NAME,
GPT_5_MODEL_NAME,
GPT_5_MINI_MODEL_NAME,
GPT_5_1_MODEL_NAME,
GPT_5_2_MODEL_NAME,
)| Concern | What digital-employee-core gives you |
|---|---|
| Identity | DigitalEmployeeIdentity, DigitalEmployeeJob, DigitalEmployeeSupervisor |
| Configuration | DigitalEmployeeConfig + ConfigTemplateLoader for YAML placeholder substitution |
| Runtime | DigitalEmployee.run(message=..., local=True) and .deploy() |
| MCPs | Pre-built connector MCP imports (google_mail_mcp, google_calendar_mcp, google_docs_mcp) |
| Tools | Utility tool imports and LangChain tool registration helpers |
| Scheduling | ScheduleItemConfig for recurring DE runs |
| Escalation | Escalation channels and supervisor wiring |
| BVT | DeploymentVerifier, BVTCheckResult, BVTStatus |
| Agents | DigitalEmployeeAgent / Agent for coordinator + sub-agents |
flowchart TB
subgraph DE["CATAPA Digital Employee"]
D["main.py / custom tools"]
end
subgraph DECore["digital-employee-core"]
Core["DigitalEmployee\nDigitalEmployeeIdentity\nMCP/tool wiring\nBVT / schedule / escalation"]
end
subgraph AIP["GL SDK / GLAIP"]
A["Agent, MCP, run_agent\nmemory, streaming, PTC, trace"]
end
D -->|imports| Core
Core -->|wraps| A
D -->|may also use directly| A
glaip-sdk is the lower-level agent runtime; digital-employee-core is the CATAPA DE abstraction on top. A DE imports from both: digital_employee_core.* for DE primitives and glaip_sdk.* when it needs direct Agent, MCP, PTC, or schedule access.
| DE / template | digital-employee-core version |
|---|---|
| Template default | 0.0.35 |
| Weekly Report | >=0.0.23 |
| Personnel Admin | >=0.0.33,<0.1.0 |
| Payroll Officer | ==0.0.31 |
| Dummy SQL | ==0.0.32 |
Pin per DE; newer isn't automatically safer because DEs depend on specific internal APIs.
- Local source:
~/Documents/github/CATAPA/CATAPA-SDK/main/python/digital-employee-core/ - Published to AWS CodeArtifact
catapa-digital-employee-corePyPI repo. - GitBook docs: same CATAPA Digital Employee section.
The same AIP agent can be reached through three front-ends:
flowchart LR
subgraph AIP["GL AIP agent"]
AGENT[agent definition\ninstruction + MCPs + tools]
end
CLI["aip CLI / TUI\nlocal terminal"]
GL["GLChat web UI\nenterprise chatbot"]
CLAUDIA["Claudia\nCATAPA-branded chat shell"]
AGENT -->|engineers run/debug| CLI
AGENT -->|end users chat| GL
AGENT -->|deployed as CATAPA product| CLAUDIA
CLAUDIA -->|embedded in| GLCHAT_BE["GLChat backend / GL API\n/message, /conversation"]
GL -->|uses| GLCHAT_BE
| Front-end | Who uses it | How it connects | Best for |
|---|---|---|---|
aip CLI / TUI |
Developers | Local SDK + aip run, aip agents, etc. |
Prototyping, verbose traces, CI/CD |
| GLChat web UI | End users / stakeholders | Browser → GLChat backend → AIP agents API | Production chat, knowledge base, feedback |
| Claudia (CATAPA) | CATAPA product users | Next.js app → /api/proxy/message → GL API /message |
White-label product experience |
Claudia is a custom Next.js application that embeds the GLChat experience under CATAPA branding. It does not replace GL AIP; it is a UI layer that proxies chat traffic to the same GLChat backend (GL_API_URL).
Key proxy flow:
- User submits message in Claudia UI.
submitUserMessageserver action builds aFormDatapayload.GLLMChatLanguageModelwraps Vercel AI SDK and posts toGL_API_URL/message.- GLChat backend forwards to the selected agent / AIP runtime.
- SSE stream returns; UI renders it.
So: GLAIP = agent runtime; GLChat = chat platform; Claudia = CATAPA-branded client on top of GLChat.
| Topic | Location |
|---|---|
| GL SDK docs | /llms-full.txt on SDK GitBook |
| GL Connectors API spec | /tmp/glair-openapi.json (or connectors.glair.ai/openapi.json) |
| GL Connectors SDK usage | skill_view:glair-connectors |
| GL Connectors HTTP endpoints | skill_view:glair-connectors-api |
| CATAPA DE GitBook | https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/ |
| CATAPA DE GitBook markdown trick | append .md to page URL |
| CATAPA DE build prompts | https://github.com/GDP-ADMIN/prompt-template/tree/main/project-wide/digital-employee |
| CATAPA DE repo | ~/Documents/github/CATAPA/digital-employee/main |
| CATAPA API/OpenAPI | ~/Documents/github/CATAPA/CATAPA-API |
| CATAPA SDK | ~/Documents/github/CATAPA/CATAPA-SDK |
Last updated: 2026-07-02. If a link breaks, prefer the local CATAPA clone or the SDK GitBook /llms-full.txt over web search snippets.
Source: https://gdplabs.gitbook.io/catapa
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
- Digital Employee Architecture
- Tech Stack Overview
- Digital Employee Detailed Block Diagram
- Multi-tenant
- Install and Configure
- Getting Started Example
- Advanced Examples
- Extend
- Instantiation
- Digital Employee Supervisor
- Sub Agents Configuration
- Skills Configuration
- MCP Allowed Tools Configuration
- Escalation Configuration
- Scheduler Configuration
- Memory Configuration
- Programmatic Tool Calling (PTC) Configuration
- Build Verification Tests (BVT)
- User Information in Digital Employee Runs
- Run History
- Recommended Project Structure
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
- Extend
- Instantiation
- Digital Employee Supervisor
- Sub Agents Configuration
- Skills Configuration
- MCP Allowed Tools Configuration
- Escalation Configuration
- Scheduler Configuration
- Memory Configuration
- Programmatic Tool Calling (PTC) Configuration
- Build Verification Tests (BVT)
- User Information in Digital Employee Runs
- Run History
- Recommended Project Structure
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Build Verification Tests (BVT) provide a pre-deployment validation step for DigitalEmployee. Before the underlying agent is deployed, BVT verifies that the resolved agent graph and its MCP integrations are usable.
BVT is implemented by DeploymentVerifier in digital_employee_core.bvt.verifier and is executed automatically by DigitalEmployee.deploy() unless you explicitly disable it.
At a high level, the default verifier checks:
- The root agent and all nested sub-agents recursively
- Every MCP attached to each agent node
- Whether the MCP URL is present and well-formed
- Whether an MCP session can actually be created and initialized
This helps catch deployment issues early, before they fail at runtime.
BVT runs during deployment:
digital_employee.deploy()By default, deploy() does this sequence:
- Build the resolved
glaip_sdk.Agentinstance - Run
self.verifier.verify(self.agent) - Raise
BuildVerificationErrorif any check failed - Continue to
agent.deploy()only when all checks passed
The implementation also supports skipping verification:
digital_employee.deploy(run_bvt=False)Use run_bvt=False only when you intentionally want to bypass pre-deployment validation.
The built-in DeploymentVerifier performs MCP-focused checks on the fully resolved agent tree:
- Recursive agent traversal: walks the top-level agent and every nested sub-agent
- URL validation: checks that each MCP config contains a URL with both scheme and host
- Session initialization: creates an MCP session and calls
session.initialize() - Authentication propagation check by execution: auth headers are built from the resolved config and used during session creation
This means BVT validates the final resolved configuration, not just the original constructor inputs.
Each check produces a BVTCheckResult with one of three statuses:
passed: the check succeededfailed: the check failed and should block deploymentskipped: the check was intentionally not run
Important behavior:
BVTResults.all_passedonly returnsFalsewhen at least one check isfailedskippedchecks do not block deployment by themselves
For example, if an MCP exists on the agent but no matching config is found, the default verifier marks it as skipped with the message No configuration found — skipped.
Deployment is blocked when any BVT check returns failed.
Common failure conditions in the default verifier include:
- MCP has no name
- MCP URL is missing or empty
- MCP URL is malformed
- MCP session initialization raises an exception
- Authentication is invalid
- The MCP endpoint is unreachable or times out
The built-in DeploymentVerifier follows this sequence:
- Reset previous results
- Run
_pre_checks(agent) - Traverse the agent tree depth-first
- Run
_check_agent_node(agent)for each visited node - Verify each MCP on that node
- Run
_post_checks(agent)
Each MCP verification does:
- Resolve the MCP config from
agent.mcp_configs - Extract the URL from
config["config"]["url"] - Validate the URL format
- Build an
MCPConfigurationforcreate_session() - Attempt real session initialization
This is not a schema-only validation. The verifier performs a real initialization attempt, which makes it useful for catching network, auth, and protocol issues.
Catch BuildVerificationError if you want to inspect or log the failure before exiting:
{% code lineNumbers="true" %}
from digital_employee_core import BuildVerificationError
try:
digital_employee.deploy()
except BuildVerificationError as exc:
print(exc){% endcode %}
The exception message is generated from BVTResults.summary(), which includes:
- Total passed checks
- Total failed checks
- Total skipped checks
- Names of failed checks
- Names of skipped checks
The verifier is injectable. DigitalEmployee accepts a verifier parameter:
{% code lineNumbers="true" %}
from digital_employee_core import DeploymentVerifier, DigitalEmployee
verifier = DeploymentVerifier(timeout=15)
digital_employee = DigitalEmployee(
identity=identity,
mcps=[google_mail_mcp],
configurations=configurations,
verifier=verifier,
){% endcode %}
You can also subclass DeploymentVerifier to add organization-specific checks.
The base class provides three hooks:
_pre_checks(agent): runs before MCP traversal_check_agent_node(agent): runs once per visited agent node_post_checks(agent): runs after traversal finishes
Each hook should yield or return BVTCheckResult instances.
The repository already includes a working example in examples/custom_deployment_verifier_example.py.
This example defines StrictDeploymentVerifier, which adds:
- Required environment variable checks
- A deployment policy check
- The standard MCP verification from the base verifier
Example structure:
{% code lineNumbers="true" %}
from collections.abc import Iterable
from glaip_sdk import Agent
from digital_employee_core import BVTCheckResult, BVTStatus, DeploymentVerifier
class StrictDeploymentVerifier(DeploymentVerifier):
def _pre_checks(self, agent: Agent) -> Iterable[BVTCheckResult]:
yield BVTCheckResult(
name="deployment_policy",
item_type="policy",
status=BVTStatus.PASSED,
message="Custom policy passed",
){% endcode %}
This pattern is useful when you want to validate things such as:
- Required environment variables
- Required configuration keys
- Service health checks
- Naming conventions
- Deployment environment policy
BVTStatus is a StrEnum with these values:
BVTStatus.PASSEDBVTStatus.FAILEDBVTStatus.SKIPPED
Represents a single check result:
{% code lineNumbers="true" %}
BVTCheckResult(
name="google_mail_mcp",
item_type="mcp",
status=BVTStatus.PASSED,
message="MCP session initialized successfully",
details={},
){% endcode %}
Fields:
name: item being checkeditem_type: category such asmcp,agent,env_var, orpolicystatus: one of theBVTStatusvaluesmessage: human-readable result descriptiondetails: optional diagnostic metadata
Aggregates all check results and provides convenience properties:
{% code lineNumbers="true" %}
results = verifier.verify(agent)
print(results.all_passed)
print(results.failed_checks)
print(results.skipped_checks)
print(results.summary()){% endcode %}
Available helpers:
all_passedfailed_checksskipped_checkssummary()
The default verifier supports MCP transports via the MCP definition on each agent node.
Behavior worth knowing:
- If an MCP does not define a transport, the verifier uses
streamable_http - For non-
stdiotransports, the generated session config includestimeout=self.timeout - For
stdio, the verifier appliesasyncio.wait_for(..., timeout=self.timeout)aroundsession.initialize()
In practice, this means the timeout constructor parameter on DeploymentVerifier controls how long initialization is allowed to take before failing.
Example:
{% code lineNumbers="true" %}
verifier = DeploymentVerifier(timeout=30){% endcode %}
The default deploy() behavior is correct for most cases. Bypass it only when you have a deliberate operational reason.
A skipped MCP check usually means the MCP exists on the agent but no resolved config was found. That may be intentional, but it is often a configuration gap worth reviewing.
If your deployment process depends on environment flags, external services, or policy enforcement, encode those rules in a custom DeploymentVerifier subclass instead of checking them manually elsewhere.
BVT runs against the resolved agent returned by _build_agent_instance(). This is the correct layer for validation because it includes propagated and merged configs.
When deployment fails, the BVT summary provides a concise diagnosis that is suitable for CI logs or deployment output.
Problem: digital_employee.deploy() raises BuildVerificationError.
What it means: At least one BVT check returned failed.
Solution:
- Inspect the exception message
- Review the failed check names
- Confirm MCP URLs are present and valid
- Confirm authentication headers are correct
- Verify the MCP endpoint is reachable from the deployment environment
Problem: A result is marked as skipped with a message like No configuration found — skipped.
What it means: The MCP exists on the agent, but no matching config entry was found in agent.mcp_configs.
Solution:
- Confirm the MCP was configured through
DigitalEmployeeConfiguration - Confirm the configuration key matches the connector template
- Confirm the config was propagated to the final built agent
Problem: The result message indicates an invalid URL format.
What it means: The resolved MCP URL is missing a scheme or host.
Solution:
- Use a full URL such as
https://example.com/mcp - Avoid bare hostnames like
example.com/mcp - Avoid empty values produced by missing environment variables
Problem: The URL looks valid, but session initialization still fails.
What it usually means:
- The endpoint is down
- Authentication is invalid
- The MCP server is not speaking the expected protocol
- Initialization timed out
Solution:
- Verify endpoint reachability
- Verify auth headers and tokens
- Increase verifier timeout if the service is slow to initialize
- Test the MCP independently if needed
Most users only need these exported symbols:
{% code lineNumbers="true" %}
from digital_employee_core import (
BuildVerificationError,
BVTCheckResult,
BVTResults,
BVTStatus,
DeploymentVerifier,
){% endcode %}
These cover:
- Failure handling with
BuildVerificationError - Modeling individual and aggregate results
- Creating default or custom verifiers
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/build-verification-tests-bvt.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
A Digital Employee Supervisor represents the human point-of-contact a Digital Employee can route decisions or blockers to.
In Digital Employee Core, a supervisor is modeled on the identity:
DigitalEmployeeIdentity.supervisorDigitalEmployeeSupervisor(name=..., email=...)
{% code lineNumbers="true" %}
import os
from digital_employee_core import DigitalEmployeeSupervisor
supervisor = DigitalEmployeeSupervisor(
name=os.getenv("SUPERVISOR_NAME", ""),
email=os.getenv("SUPERVISOR_EMAIL", ""),
){% endcode %}
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployeeIdentity, DigitalEmployeeJob
job = DigitalEmployeeJob(
title="Operations Assistant",
description="A digital employee that can escalate to a supervisor when blocked",
instruction=(
"You help with operational tasks. "
"If a tool fails or you encounter a critical blocker, follow the escalation protocol."
),
)
identity = DigitalEmployeeIdentity(
name="Ops Assistant - Example",
email="ops.assistant@example.com",
job=job,
supervisor=supervisor,
){% endcode %}
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/digital-employee-supervisor.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Escalation allows a Digital Employee to route a task to a human supervisor (see Digital Employee Supervisor) when it is blocked (e.g., tool failures, missing permissions, critical ambiguity). Escalation is opt-in and must be explicitly enabled.
In this repository, see examples/escalation_example.py for a working end-to-end example.
Escalation is designed to reach a supervisor configured on the Digital Employee identity:
DigitalEmployeeIdentity.supervisorDigitalEmployeeSupervisor(name=..., email=...)
If escalation is enabled and a supervisor exists, the escalation protocol is included in the generated prompt.
Escalation is delivered via one or more channels (e.g., email).
Channels can introduce additional runtime dependencies (MCP connectors/tools). This matters at deployment time.
In this guide, we will use the GoogleMailMCPEscalationChannel as an example.
{% code lineNumbers="true" %}
import os
from digital_employee_core import (
DigitalEmployee,
DigitalEmployeeConfiguration,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
DigitalEmployeeSupervisor,
)
from digital_employee_core.connectors.mcps import google_docs_mcp
from digital_employee_core.escalation.channels.google_mail_mcp_channel import (
GoogleMailMCPEscalationChannel,
){% endcode %}
{% code lineNumbers="true" %}
job = DigitalEmployeeJob(
title="Operations Assistant",
description="A digital employee that can escalate to a supervisor when blocked",
instruction=(
"You help with operational tasks. "
"If a tool fails or you encounter a critical blocker, follow the escalation protocol."
),
){% endcode %}
{% hint style="warning" %} Important: Use the term "escalation protocol" in the Digital Employee instructions whenever we need to refer to or trigger the escalation flow. {% endhint %}
{% code lineNumbers="true" %}
supervisor = DigitalEmployeeSupervisor(
name=os.getenv("SUPERVISOR_NAME", ""),
email=os.getenv("SUPERVISOR_EMAIL", ""),
){% endcode %}
{% hint style="info" %} Note: The following environment variables are required to enable escalation:
SUPERVISOR_NAMESUPERVISOR_EMAIL{% endhint %}
{% code lineNumbers="true" %}
identity = DigitalEmployeeIdentity(
name="Ops Assistant - Example",
email="ops.assistant@example.com",
job=job,
supervisor=supervisor,
){% endcode %}
{% code lineNumbers="true" %}
configurations = [
DigitalEmployeeConfiguration(key="GOOGLE_MAIL_MCP_URL", value=os.getenv("GOOGLE_MAIL_MCP_URL", "")),
DigitalEmployeeConfiguration(key="GOOGLE_DOCS_MCP_URL", value=os.getenv("GOOGLE_DOCS_MCP_URL", "")),
DigitalEmployeeConfiguration(key="GOOGLE_MCP_X_API_KEY", value=os.getenv("GOOGLE_MCP_X_API_KEY", "")),
]{% endcode %}
{% hint style="info" %} Note: The following environment variables are required to enable the Google Mail MCP escalation channel:
GOOGLE_MAIL_MCP_URLGOOGLE_MCP_X_API_KEY
These values are typically passed into DigitalEmployeeConfiguration so they become part of the deployed agent configuration. Adjust the environment variables based on your escalation channel.
{% endhint %}
{% code lineNumbers="true" %}
digital_employee = DigitalEmployee(
identity=identity,
mcps=[google_docs_mcp],
configurations=configurations,
){% endcode %}
{% code lineNumbers="true" %}
digital_employee.enable_escalation()
digital_employee.add_escalation_channel(GoogleMailMCPEscalationChannel()){% endcode %}
{% code lineNumbers="true" %}
digital_employee.deploy()
result = digital_employee.run(message="Read google docs with document_id='123413'"){% endcode %}
- Enable escalation and add channels before calling
digital_employee.deploy(). - The deploy step will bundle any required MCP connectors/tools introduced by escalation channels.
Escalation channels are extensible. To create your own channel, implement EscalationChannel (see digital_employee_core/escalation/base_escalation_channel.py) and register it via digital_employee.add_escalation_channel(...).
Create a new class that extends EscalationChannel that defines:
get_required_mcps(): MCP connectors the channel needs at runtime.get_required_tools(): Additional tools (if any). Return[]if not needed.get_prompt_header(): Short title shown in the escalation protocol.get_prompt_body(supervisor, ...): Concrete instructions describing how the agent should escalate using your tools/MCP.
Minimal skeleton:
{% code lineNumbers="true" %}
from typing import Any
from glaip_sdk import MCP, Tool
from digital_employee_core.escalation.base_escalation_channel import EscalationChannel
from digital_employee_core.identity.identity import DigitalEmployeeSupervisor
class CustomEscalationChannel(EscalationChannel):
def get_required_mcps(self) -> list[MCP]:
return []
def get_required_tools(self) -> list[Tool]:
return []
def get_prompt_header(self, **kwargs: Any) -> str:
return "Custom Escalation"
def get_prompt_body(self, supervisor: DigitalEmployeeSupervisor, **kwargs: Any) -> str:
return (
f"When blocked, notify {supervisor.name} ({supervisor.email}) using <YOUR_TOOL>. "
"Include: timestamp, failed action, what you tried, and what you need from the supervisor."
){% endcode %}
For a reference implementation, see digital_employee_core/escalation/channels/google_mail_mcp_channel.py.
digital_employee.enable_escalation()
digital_employee.add_escalation_channel(CustomEscalationChannel())
digital_employee.deploy()- One instance per channel type:
EscalationChannelequality is based on class type, so adding the same channel type multiple times is treated as a duplicate. - Tool restrictions: If your channel uses MCP tools, ensure those tools are allowed (if you are using whitelisted MCP tools, see MCP Allowed Tools Configuration).
- Define clear escalation triggers in
DigitalEmployeeJob.instruction(e.g., tool failures, permission issues, urgent blockers). - Configure least-privilege tools for MCPs where possible (see MCP Allowed Tools Configuration).
- Validate supervisor identity (non-empty name/email) during development to avoid “silent” non-actionable escalations.
- Test with prompt preview (
build_prompt()) before deploying.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/escalation-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Some digital employee core components can be extended, for example, identity, connectors (tools & MCPs), and the digital employee object itself.
Digital employee core can be extended using this code example:
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployeeIdentity
class ExtendedDigitalEmployeeIdentity(DigitalEmployeeIdentity):
"""Extended Digital Employee Identity with additional attributes."""
employee_id: str{% endcode %}
We can add our own tools in addition to those already provided by GL Connectors by extending the BaseTool.
Here is the example:
{% code lineNumbers="true" %}
import calendar
from datetime import datetime, timedelta
from typing import Any
from gllm_plugin.tools import tool_plugin
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
class InterviewDateInput(BaseModel):
"""Input schema for interview date tool."""
reference_date: str = Field(
default=None,
description="Reference date in ISO format (YYYY-MM-DD, e.g., 2024-12-16). If not provided, uses current date.",
)
class GenerateInterviewDateConfig(BaseModel):
"""Configuration schema for interview date generation."""
days_to_add: int = Field(
default=7,
description="Number of days to add to the reference date before finding the next available weekday",
)
excluded_weekdays: list[int] = Field(
default=[calendar.SATURDAY, calendar.SUNDAY],
description=(
"List of weekday numbers to exclude (0=Monday, 1=Tuesday, "
"..., 6=Sunday). Default excludes Saturday and Sunday."
),
)
@tool_plugin(version="1.0.0")
class ConfigurableGenerateInterviewDateTool(BaseTool):
"""Generate an interview date with configurable days offset and excluded weekdays."""
name: str = "configurable_generate_interview_date"
description: str = (
"Generate interview date: adds configurable days to reference date, "
"returns next available weekday excluding configured weekdays."
)
args_schema: type[BaseModel] = InterviewDateInput
tool_config_schema: type[BaseModel] = GenerateInterviewDateConfig
def _run(
self,
reference_date: str | None = None,
config: RunnableConfig = None,
**_kwargs: Any,
) -> str:
"""Generate interview date with configuration.
Adds a configurable number of days to the reference date and returns
the next available weekday, excluding configured weekdays.
Args:
reference_date (str | None, optional): Reference date in ISO format
(YYYY-MM-DD, e.g., 2024-12-16). If not provided, uses current
date. Defaults to None.
config (RunnableConfig, optional): Runnable configuration containing
tool settings. Defaults to None.
**_kwargs (Any): Additional keyword arguments (ignored).
Returns:
str: Interview date in ISO format (YYYY-MM-DD) or error message if
date format is invalid.
"""
try:
# Get tool config
tool_config = self.get_tool_config(config)
except Exception as e:
return f"Error: Failed to retrieve tool configuration. " f"Details: {e}"
try:
# Parse reference date
base_date = (
datetime.strptime(reference_date, "%Y-%m-%d").date() if reference_date else datetime.now().date()
)
except ValueError as e:
return f"Error: Invalid date format. Please use YYYY-MM-DD " f"(e.g., 2024-12-16). Details: {e}"
# Add configured days to base date
target_date = base_date + timedelta(days=tool_config.days_to_add)
# Find next available weekday (not in excluded list)
max_iterations = 7 # Prevent infinite loop
iterations = 0
while target_date.weekday() in tool_config.excluded_weekdays and iterations < max_iterations:
target_date += timedelta(days=1)
iterations += 1
if iterations >= max_iterations:
return (
f"Error: Could not find available weekday after "
f"{max_iterations} days. All weekdays may be excluded."
)
return f"Interview date: {target_date.isoformat()}"{% endcode %}
To configure the tools, we need to create a config_templates/tools_configs.yaml file. Here is an example of tools_configs.yaml :
configurable_generate_interview_date:
days_to_add: ${INTERVIEW_DAYS_TO_ADD}
excluded_weekdays: ${INTERVIEW_EXCLUDED_WEEKDAYS}To define default value for configs, create a config_templates/defaults.yaml file. Here is an example of defaults.yaml :
INTERVIEW_DAYS_TO_ADD: 7
INTERVIEW_EXCLUDED_WEEKDAYS: 5,6Here is how we can create our own MCP:
{% code lineNumbers="true" %}
from glaip_sdk.mcps import MCP
new_google_calendar_mcp = MCP(
name="new_google_calendar_mcp",
description="MCP for Google Calendar Operation for DE",
transport="http",
config={"url": "https://default.com/google_calendar/mcp"},
){% endcode %}
We need to provide the config_templates/mcp_configs.yaml file to be able to use the MCP. Here is an example of mcp_configs.yaml:
new_google_calendar_mcp:
config:
url: ${NEW_GOOGLE_CALENDAR_MCP_URL}
authentication:
type: api-key
key: X-API-Key
value: ${NEW_GOOGLE_MCP_X_API_KEY}{% code lineNumbers="true" %}
from digital_employee_core import (
DEFAULT_MODEL_NAME,
ConfigTemplateLoader,
)
from gllm_core.utils import LoggerManager
from typing import Any
logger = LoggerManager().get_logger(__name__)
class ExtendedDigitalEmployee(DigitalEmployee):
"""Extended Digital Employee with extended tool and MCP configurations.
This subclass demonstrates how to extend the base DigitalEmployee
with additional specific configurations.
"""
def __init__(
self,
identity: DigitalEmployeeIdentity,
tools: list[Any] | None = None,
sub_agents: list[Any] | None = None,
mcps: list[Any] | None = None,
configurations: list[DigitalEmployeeConfiguration] | None = None,
model: str | None = DEFAULT_MODEL_NAME,
):
"""Initialize the Specific Digital Employee.
Args:
identity (DigitalEmployeeIdentity): The Digital Employee's identity.
tools (list[Any] | None, optional): List of tools the Digital Employee can use. Defaults to None.
sub_agents (list[Any] | None, optional): List of sub-agents (for future use). Defaults to None.
mcps (list[Any] | None, optional): List of MCPs the Digital Employee can use. Defaults to None.
configurations (list[DigitalEmployeeConfiguration] | None, optional): List of configuration objects.
Defaults to None.
model (str | None, optional): Model identifier to use for the
agent. Defaults to DEFAULT_MODEL_NAME.
"""
super().__init__(
identity=identity,
tools=tools,
sub_agents=sub_agents,
mcps=mcps,
configurations=configurations,
model=model,
)
# Create a separate config loader for specific templates
# You can place tool_configs.yaml and mcp_configs.yaml in a separate directory
additional_config_dir = Path(__file__).parent / "config_templates"
additional_config_loader = ConfigTemplateLoader(template_dir=additional_config_dir)
# Simply add the additional config loader - the base class handles the rest!
# build_prompt() will use all loaders automatically
# build_tool_config() and build_mcp_config() will merge configs from all loaders
self.add_config_loader(additional_config_loader){% endcode %}
{% hint style="info" %} To see more examples, please check the Digital Employee Example. {% endhint %}
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/extend.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
For these examples, you will need to:
- Complete the Extend section
{% code lineNumbers="true" %}
from extended_digital_employee.connectors.mcps import new_google_calendar_mcp # new MCP
from extended_digital_employee.connectors.tools import ConfigurableGenerateInterviewDateTool # new Tool
from extended_digital_employee.digital_employee import ExtendedDigitalEmployee
from extended_digital_employee.identity import ExtendedDigitalEmployeeIdentity
from glaip_sdk import Tool
from digital_employee_core import (
DigitalEmployeeConfiguration,
DigitalEmployeeJob,
){% endcode %}
{% code lineNumbers="true" %}
job = DigitalEmployeeJob(
title="Recruitment Coordinator",
description="Coordinates technical interviews and manages candidate scheduling",
instruction="When candidates pass the initial screening, schedule their technical interview. Always confirm the date clearly and provide a warm and professional response.",
)
extended_identity = ExtendedDigitalEmployeeIdentity(
name="Alex Morgan", email="alex.morgan@example.com", job=job, employee_id="EMP-123"
)
configurations = [
DigitalEmployeeConfiguration(key="INTERVIEW_DAYS_TO_ADD", value="10"),
DigitalEmployeeConfiguration(key="INTERVIEW_EXCLUDED_WEEKDAYS", value="5,6"),
DigitalEmployeeConfiguration(key="NEW_GOOGLE_CALENDAR_MCP_URL", value="https://api.bosa.id/google_calendar/mcp"),
DigitalEmployeeConfiguration(key="NEW_GOOGLE_MCP_X_API_KEY", value="[gl-connectors-x-api-key]"),
]
# Initialize extended digital employee
extended_digital_employee = ExtendedDigitalEmployee(
identity=extended_identity,
mcps=[new_google_calendar_mcp],
tools=[Tool.from_langchain(ConfigurableGenerateInterviewDateTool)],
configurations=configurations,
)
extended_digital_employee.deploy(){% endcode %}
{% hint style="info" %}
In this example, we use new MCPs (mcps=[new_google_calendar_mcp]) and tools (tools=[Tool.from_langchain(ConfigurableGenerateInterviewDateTool)]) that are specifically created for the extended digital employee.
{% endhint %}
{% hint style="info" %}
We also use INTERVIEW_DAYS_TO_ADD, INTERVIEW_EXCLUDED_WEEKDAYS, NEW_GOOGLE_CALENDAR_MCP_URL and NEW_GOOGLE_MCP_X_API_KEY configurations, which are defined in the config template of the new tool and MCP.
{% endhint %}
{% code lineNumbers="true" %}
# Run the extended digital employee using a prompt
result = extended_digital_employee.run(
message="I need to schedule a technical interview for a software engineer candidate who just passed the screening round. Can you generate an interview date?",
){% endcode %}
{% hint style="info" %} Before running the sample code, replace the following placeholders:
- Replace
[gl-connectors-x-api-key]with x-api-key from GL Connectors. See below for one way to do it. - (Optional) Replace
NEW_GOOGLE_CALENDAR_MCP_URLif you are using a different GL Connectors server instance.
🔑 Get GL Connectors x-api-key
- Open https://api.bosa.id/console, then sign in.
- In the Credentials section, expand the x-api-key panel and click Copy combined value button. Paste this value to replace
[gl-connectors-x-api-key].
- If your Gmail account has not been integrated yet, continue with the steps below.
- Under Available Modules section, find the Google_mail integration and click Add New Integration button.
- An authorization URL will appear. Click or copy the URL, then authenticate using your Gmail account.
- Below is an example of a successfully integrated Gmail account.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/instantiation.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
The allowed_tools configuration enables you to restrict which tools a Digital Employee can access from Model Context Protocol (MCP) servers. This is useful for security, cost control, and ensuring your Digital Employee only uses approved functionality.
Allowed tools are a whitelist of specific tool names that an MCP connector is permitted to use. When configured, the Digital Employee will only have access to the specified tools from that MCP, even if the MCP server provides additional capabilities.
- Security: Limit access to sensitive operations
- Cost Control: Prevent usage of expensive API calls
- Compliance: Ensure only approved tools are used
- Clarity: Make it explicit which capabilities are available
Allowed tools are configured using DigitalEmployeeConfiguration objects with specific key patterns:
{% code lineNumbers="true" %}
DigitalEmployeeConfiguration(
key="<MCP_NAME>_ALLOWED_TOOLS",
value="tool1,tool2,tool3"
){% endcode %}
The configuration key follows this pattern:
<MCP_NAME>_ALLOWED_TOOLS
Where <MCP_NAME> matches the MCP connector's configuration prefix (e.g., GOOGLE_MAIL_MCP, GOOGLE_CALENDAR_MCP).
The value is a comma-separated string of tool names that will be automatically converted to a list:
{% code lineNumbers="true" %}
# This string...
value="google_mail_send_email,google_mail_get_email_details,google_mail_list_emails"
# ...is automatically converted to this list:
['google_mail_send_email', 'google_mail_get_email_details', 'google_mail_list_emails']{% endcode %}
{% code lineNumbers="true" %}
import os
from dotenv import load_dotenv
from digital_employee_core import (
DigitalEmployee,
DigitalEmployeeConfiguration,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
)
from digital_employee_core.connectors.mcps import google_calendar_mcp, google_mail_mcp
load_dotenv(){% endcode %}
{% code lineNumbers="true" %}
job = DigitalEmployeeJob(
title="Email Assistant",
description="Helps manage emails and calendars",
instruction="You are an email assistant that helps users manage their emails efficiently.",
)
identity = DigitalEmployeeIdentity(
name="Email Bot",
email="emailbot@example.com",
job=job,
){% endcode %}
{% code lineNumbers="true" %}
configurations = [
# MCP URLs
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_URL",
value=os.getenv("GOOGLE_MAIL_MCP_URL", ""),
),
DigitalEmployeeConfiguration(
key="GOOGLE_MCP_X_API_KEY",
value=os.getenv("GOOGLE_MCP_X_API_KEY", ""),
),
# Allowed tools - comma-separated strings
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_ALLOWED_TOOLS",
value="google_mail_send_email,google_mail_get_email_details,google_mail_list_emails",
),
]{% endcode %}
{% hint style="info" %} Note: Replace the example values above with your actual configuration:
GOOGLE_MAIL_MCP_URL: Your MCP server endpointsGOOGLE_MCP_X_API_KEY: Your actual API key (consider using environment variables) {% endhint %}
{% code lineNumbers="true" %}
# Create digital employee with MCPs
mcps = [google_mail_mcp]
digital_employee = DigitalEmployee(
identity=identity,
mcps=mcps,
configurations=configurations,
)
# Deploy applies the configurations
digital_employee.deploy(){% endcode %}
If you want to verify the configuration was applied correctly, you can check the deployed MCP config:
{% code lineNumbers="true" %}
mail_mcp_config = digital_employee.agent.mcp_configs.get(google_mail_mcp.name).get("config")
print(f"Mail MCP allowed_tools in config: {mail_mcp_config.get('allowed_tools')}")
# Output: ['google_mail_send_email', 'google_mail_read_email', 'google_mail_search']{% endcode %}
Now you can run the Digital Employee and it will only have access to the allowed tools:
{% code lineNumbers="true" %}
# The Digital Employee will only be able to use the allowed tools
response = digital_employee.run(
message="Read and summarize my latest email"
)
print(response){% endcode %}
In this example:
- The Digital Employee can use
google_mail_get_email_details,google_mail_list_emailsto find the latest email. - It cannot use tools like
google_mail_delete_emailbecause they weren't in the allowed list.
For the list of tools that are available via GLConnector, please refer to https://api.bosa.id/docs.
Only allow reading and searching emails, but not sending:
{% code lineNumbers="true" %}
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_ALLOWED_TOOLS",
value="google_mail_get_email_details,google_mail_list_emails",
){% endcode %}
Only allow listing events, but not creating or modifying:
{% code lineNumbers="true" %}
DigitalEmployeeConfiguration(
key="GOOGLE_CALENDAR_MCP_ALLOWED_TOOLS",
value="google_calendar_events_list",
){% endcode %}
Grant access to multiple related tools:
{% code lineNumbers="true" %}
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_ALLOWED_TOOLS",
value="google_mail_send_email,google_mail_get_email_details,google_mail_delete_email",
){% endcode %}
Only grant access to tools that are absolutely necessary for the Digital Employee's job:
{% code lineNumbers="true" %}
# Good: Specific tools for specific job
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_ALLOWED_TOOLS",
value="google_mail_get_email_details,google_mail_list_emails",
)
# Avoid: Granting all available tools without restriction{% endcode %}
Add comments explaining why specific tools are allowed:
{% code lineNumbers="true" %}
# Allow email reading and searching for customer support queries
DigitalEmployeeConfiguration(
key="GOOGLE_MAIL_MCP_ALLOWED_TOOLS",
value="google_mail_get_email_details,google_mail_list_emails",
){% endcode %}
Store API keys and URLs in environment variables:
{% code lineNumbers="true" %}
import os
DigitalEmployeeConfiguration(
key="GOOGLE_MCP_X_API_KEY",
value=os.getenv("GOOGLE_MCP_API_KEY"),
){% endcode %}
Problem: All tools are still accessible despite configuration.
Solution: Ensure the configuration key matches the MCP's expected pattern:
- Check the MCP connector's documentation for the correct prefix
- Verify the key format:
<MCP_PREFIX>_ALLOWED_TOOLS
Problem: Tools are not recognized.
Solution: Verify the exact tool names from the MCP server documentation. Tool names are case-sensitive and must match exactly.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/mcp-allowed-tools-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Digital Employee Core supports user-scoped memory so an agent can remember facts and preferences across multiple calls. Memory is opt-in:
- Enable a memory provider in
agent_config. - Pass a stable
memory_user_idon everyrun()/arun()call.
memory_user_id is the user identifier used to scope memory.
- Same
memory_user_id+ same agent => the agent can recall previously stored facts. - Different
memory_user_id+ same agent => isolated memory (no cross-user leakage). - Same
memory_user_id+ different agent => isolated memory (each agent maintains its own memory scope).
Memory is enabled by setting AgentConfigKeys.MEMORY to a provider (e.g. MemoryProvider.MEM0). Currently we utilize GL SDK Memory via AIP Memory.
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployee, DigitalEmployeeIdentity, DigitalEmployeeJob
from digital_employee_core.configuration.agent_configuration import AgentConfigKeys, MemoryProvider
job = DigitalEmployeeJob(
title="Memory-Enabled Assistant",
description="A digital employee that can remember user-specific facts across calls",
instruction=(
"When the user tells you a personal preference or fact, remember it for future conversation."
),
)
identity = DigitalEmployeeIdentity(name="memory_assistant", email="memory.assistant@example.com", job=job)
digital_employee = DigitalEmployee(
identity=identity,
agent_config={AgentConfigKeys.MEMORY: MemoryProvider.MEM0},
){% endcode %}
{% code lineNumbers="true" %}
digital_employee.deploy()
memory_user_id = "user-123"
# Store a preference
result_1 = digital_employee.run(
message="My favorite color is beige. Please remember this for next time.",
memory_user_id=memory_user_id,
)
# Recall later
result_2 = digital_employee.run(
message="What is my favorite color?",
memory_user_id=memory_user_id,
){% endcode %}
{% hint style="info" %}
Note: After storing a memory, there may be a slight delay from the provider before it becomes available for retrieval, so you may need to wait a moment (manually or via Python's sleep() ) before querying for recently stored information.
{% endhint %}
{% code lineNumbers="true" %}
other_user_id = "user-456"
result_3 = digital_employee.run(
message="What is my favorite color?",
memory_user_id=other_user_id,
){% endcode %}
To use MemoryProvider.MEM0 locally, you must provide MEM0_API_KEY in your environment variables.
export MEM0_API_KEY="<your_api_key>"If you load environment variables via .env, ensure your entrypoint calls load_dotenv().
Then, run the digital employee with local=True to run it locally.
{% code lineNumbers="true" %}
result_3 = digital_employee.run(
message="What is my favorite color?",
memory_user_id=memory_user_id,
local=True,
){% endcode %}
memory_user_idis required when memory is enabled; the agent cannot store or recall memories without it.- Use stable identifiers (e.g., internal user ID) and do not use PII (like emails) unless necessary.
- Write clear instructions in the job prompt indicating what to remember (preferences, long-term facts) vs what not to (secrets).
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/memory-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Programmatic Tool Calling (PTC) lets a Digital Employee orchestrate tools through Python code executed in a sandboxed environment, rather than making individual API round-trips for each tool call.
When PTC is enabled, the agent gets access to an execute_ptc_code tool. The agent can write Python code that calls its registered tools, processes intermediate results, and returns only the final output to its context window.
Note: PTC is currently only supported for local runs (
run(..., local=True)).
For the canonical guide, see the AIP PTC Guide.
| Benefit | Description |
|---|---|
| Context Window Protection | Intermediate results stay in the sandbox — only the final output is returned to the agent's context |
| Parallel Execution | The agent can call multiple tools concurrently within a single code block |
| Reduced Inference Overhead | One model pass writes the code; execution replaces multiple model-tool-model round-trips |
- The agent receives a task requiring multiple tool calls.
- The agent writes a Python script and invokes
execute_ptc_code. - The script runs inside an E2B sandbox with all registered tools available.
- Only the final printed output is returned to the agent's context.
PTC is configured via the PTC class from glaip_sdk.ptc:
{% code lineNumbers="true" %}
from glaip_sdk.ptc import PTC
ptc_config = PTC(
enabled=True,
sandbox_timeout=120.0,
){% endcode %}
The instance is passed to DigitalEmployee via the ptc parameter. Tools registered on the Digital Employee are automatically made available in the sandbox — you do not need to configure them separately.
E2B_API_KEYmust be set (get one at https://e2b.dev)OPENAI_API_KEY(or another supported model key) must be setglaip-sdkinstalled with[local]extras
The file examples/ptc/ptc_example.py demonstrates the PTC workflow.
{% code lineNumbers="true" %}
from dotenv import load_dotenv
from glaip_sdk import Tool
from glaip_sdk.ptc import PTC
from digital_employee_core import (
DigitalEmployee,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
)
from digital_employee_core.connectors.tools.utility_tools import time_tool
from examples.ptc.tools.calculator_tool import CalculatorTool
load_dotenv(){% endcode %}
{% code lineNumbers="true" %}
job = DigitalEmployeeJob(
title="PTC Assistant",
description="A helpful assistant with Programmatic Tool Calling enabled",
instruction=(
"You are a helpful assistant with Programmatic Tool Calling (PTC) enabled. "
"When you need to orchestrate multiple tool calls or process data programmatically, "
"you can write Python code using the execute_ptc_code tool.\n\n"
"Use PTC when you need to:\n"
"1. Call multiple tools and process their results\n"
"2. Perform calculations or data transformations\n"
"3. Keep intermediate results out of context\n\n"
"Provide clear and helpful responses."
),
)
identity = DigitalEmployeeIdentity(
name="PTC Assistant",
email="ptc.assistant@example.com",
job=job,
){% endcode %}
{% code lineNumbers="true" %}
ptc_config = PTC(
enabled=True,
sandbox_timeout=120.0, # Maximum execution time in seconds, optional
)
calculator_tool = Tool.from_langchain(CalculatorTool)
digital_employee = DigitalEmployee(
identity=identity,
tools=[time_tool, calculator_tool],
ptc=ptc_config,
){% endcode %}
All tools passed to DigitalEmployee are automatically available inside the PTC sandbox.
{% code lineNumbers="true" %}
message = "Calculate what time it will be in 3.5 hours. Directly show me the time without any intermediate output."
result = digital_employee.run(message=message, local=True)
print(result){% endcode %}
Note: Do not call
deploy()for local PTC runs. Userun(..., local=True)directly.
- PTC is activated with
PTC(enabled=True). - Tools are registered on the Digital Employee — no separate sandbox configuration needed.
- The agent can orchestrate
time_toolandcalculator_toolin a single code block. - Intermediate results (e.g., the raw time value) stay in the sandbox; only the final answer is returned to the model context.
| Parameter | Type | Default | Description |
|---|---|---|---|
enabled |
bool |
False |
Activates PTC. Must be True to use PTC. |
sandbox_timeout |
float |
120.0 |
Maximum execution time (seconds) for a sandbox run. |
default_tool_timeout |
float |
60.0 |
Per-tool call timeout (seconds) inside the sandbox. |
sandbox_template |
str | None |
"aip-agents-ptc-v1" |
E2B sandbox template identifier. |
prompt |
dict | None |
None |
Prompt configuration for the execute_ptc_code tool description. Accepts {"mode": "...", "include_example": bool}. |
ptc_packages |
list[str] | None |
None |
Additional Python packages to install in the sandbox. None uses smart package selection; an explicit list is additive to the defaults. |
Not supported:
custom_tools— tools are always auto-derived from theDigitalEmployee.toolslist.
The agent can call multiple tools concurrently in one code block, without extra round-trips:
{% code lineNumbers="true" %}
# Agent-generated code running inside the sandbox
result_a = time_tool()
result_b = calculator_tool(expression="365 * 24")
print(f"Time: {result_a}, Hours in a year: {result_b}"){% endcode %}
{% code lineNumbers="true" %}
ptc_config = PTC(
enabled=True,
ptc_packages=["pandas==2.2.0", "numpy"],
){% endcode %}
{% code lineNumbers="true" %}
ptc_config = PTC(
enabled=True,
sandbox_timeout=300.0, # 5 minutes
default_tool_timeout=90.0,
){% endcode %}
Explicitly tell the agent when and how to use PTC in the instruction field:
{% code lineNumbers="true" %}
instruction=(
"When orchestrating multiple tool calls, use execute_ptc_code to run them "
"in a single Python block and return only the final result."
){% endcode %}
Without this hint, the agent may fall back to individual tool calls.
A short timeout is fine for quick calculations; increase it for tasks that involve many sequential tool calls or heavy data processing.
If ptc_packages is None (the default), aip-agents selects packages automatically based on which tools are registered. Only set it explicitly when you need a package that is not auto-detected.
PTC is a local-only feature. Call run(..., local=True) directly — skip deploy().
Constructing a new DigitalEmployee on every request means a cold sandbox start for each run. Reuse the instance across calls to amortize sandbox startup time.
Problem: The sandbox fails to start with an authentication error.
Solution: Obtain an API key from https://e2b.dev and add it to your .env file:
{% code lineNumbers="true" %}
E2B_API_KEY=your_key_here{% endcode %}
Problem: Import errors or missing sandbox dependencies.
Solution: Install the local extras:
{% code lineNumbers="true" %}
poetry add "glaip-sdk[local]"{% endcode %}
Problem: The agent calls tools individually instead of using PTC.
Solution: Add explicit PTC guidance to the job instruction. The model needs to be told when PTC is the preferred approach (see Best Practice 1 above).
Problem: Execution is cut off mid-run with a timeout error.
Solution: Increase sandbox_timeout and, if tools are slow, default_tool_timeout:
{% code lineNumbers="true" %}
ptc_config = PTC(
enabled=True,
sandbox_timeout=300.0,
default_tool_timeout=90.0,
){% endcode %}
Problem: The agent's PTC code raises an import or NameError for a registered tool.
Solution: Verify the tool is passed to DigitalEmployee(tools=[...]). Tools are auto-derived from this list — no additional sandbox configuration is required.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/programmatic-tool-calling-ptc-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Here is the recommended project structure when building a digital employee.
digital-employee/
├── agents/
│ ├── agent_1.py
│ ├── agent_2.py
│ └── agent_3.py
├── config_templates/
│ ├── defaults.yaml
│ ├── mcp_configs.yaml
│ └── tool_configs.yaml
├── connectors/
│ ├── mcps/
│ │ ├── mcp_1.py
│ │ ├── mcp_2.py
│ │ └── mcp_3.py
│ └── tools/
│ ├── tool_1.py
│ ├── tool_2.py
│ └── tool_3.py
├── identity/
│ └── identity.py
└── main.py
The agents folder contains a list of agents used by the digital employee to perform tasks. This is optional and depends on the digital employee's use cases.
Config templates contain mcp_configs.yaml and tool_configs.yaml, which are configurations used when running the MCPs and tools. You can also add defaults.yaml to store your default values for each MCP and tool configuration.
The connectors folder contains MCPs and tools used by that specific digital employee. Please see the Extend section to learn how to create new MCPs and tools.
Identity contains the extended identity of the digital employee if needed. This folder is optional if all the identity from the digital employee core is sufficient.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/recommended-project-structure.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
We use GLAIP Audit Trails to review a Digital Employee agent’s run history for auditing.
Run:
aipIn the CLI:
/agents
Find the agent by Digital Employee name, then select the matching agent entry.
In the CLI:
/runs
The console will display a TUI list of runs (run history) for the selected agent.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/run-history.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
The Scheduler feature enables you to configure Digital Employees to execute tasks automatically at specified times using cron-based scheduling. This is ideal for recurring tasks, automated reports, periodic checks, and time-based workflows.
Currently we utilize AIP Scheduled Run feature to manage these workflows.
{% hint style="info" %} Note: Schedules are only available for remote runs (deployed Digital Employees). Local runs do not support scheduled execution. {% endhint %}
A scheduler allows your Digital Employee to run tasks automatically based on time-based triggers. Each schedule consists of:
- Schedule Configuration: Defines when the task should run (using cron syntax)
- Input: The message or instruction to execute when the schedule triggers
- Automation: Execute tasks without manual intervention
- Consistency: Ensure tasks run at predictable times
- Efficiency: Free up human resources for higher-value work
- Reliability: Never miss scheduled tasks or reminders
Schedules are configured using two main classes:
ScheduleConfig: Defines the timing using cron-like parametersScheduleItemConfig: Combines the schedule with the input to execute
The ScheduleConfig class uses cron-style parameters:
{% code lineNumbers="true" %}
from glaip_sdk.models.schedule import ScheduleConfig
schedule = ScheduleConfig(
minute="0", # 0-59 or "*" for every minute
hour="8", # 0-23 or "*" for every hour
day_of_month="*", # 1-31 or "*" for every day
month="*", # 1-12 or "*" for every month
day_of_week="0-4", # 0-6 (0=Monday, 6=Sunday) or "*" for every day
){% endcode %}
Reference to GLAIP Python SDK.
| Field | Values | Special Characters | Examples |
|---|---|---|---|
minute |
0-59 | * (every), - (range), , (list) |
0, */15, 0,30 |
hour |
0-23 | * (every), - (range), , (list) |
8, 9-17, 8,12,18 |
day_of_month |
1-31 | * (every), - (range), , (list) |
1, 1-15, 1,15 |
month |
1-12 | * (every), - (range), , (list) |
*, 1-6, 1,7 |
day_of_week |
0-6 | * (every), - (range), , (list) |
0-4, 0,6, * |
Note: Day of week starts with Monday (0) and ends with Sunday (6).
{% code lineNumbers="true" %}
from glaip_sdk.models.schedule import ScheduleConfig
from digital_employee_core import (
DigitalEmployee,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
)
from digital_employee_core.schedule import ScheduleItemConfig{% endcode %}
{% code lineNumbers="true" %}
# Define the job
job = DigitalEmployeeJob(
title="Friendly Greeter",
description="A friendly digital employee that greets people at different times",
instruction="You are a friendly greeter. Always respond with a cheerful greeting!",
)
# Create identity
identity = DigitalEmployeeIdentity(
name="Sunny",
email="sunny@example.com",
job=job,
){% endcode %}
{% code lineNumbers="true" %}
# Morning greeting - weekdays at 8 AM
morning_schedule = ScheduleConfig(
minute="0",
hour="8",
day_of_month="*",
month="*",
day_of_week="0-4", # Monday to Friday
){% endcode %}
{% code lineNumbers="true" %}
# Create schedule item with input
morning_schedule_item = ScheduleItemConfig(
schedule_config=morning_schedule,
input="Morning greeting"
){% endcode %}
{% code lineNumbers="true" %}
# Create Digital Employee with schedule
digital_employee = DigitalEmployee(
identity=identity,
schedules=[morning_schedule_item],
)
# Deploy the Digital Employee (this also creates the schedule)
digital_employee.deploy(){% endcode %}
{% code lineNumbers="true" %}
# Get schedules from the Digital Employee
schedules = digital_employee.get_schedule()
print(f"Number of schedules: {len(schedules)}")
# Display schedule details
for i, schedule_item in enumerate(schedules, 1):
print(f"Schedule {i}:")
print(f" Input: {schedule_item.input}")
print(f" Cron: {schedule_item.schedule_config.to_cron_string()}"){% endcode %}
{% code lineNumbers="true" %}
from glaip_sdk import Client
# Get the deployed agent
client = Client()
agent = client.get_agent_by_id(digital_employee.agent.id)
# List all schedules
agent_schedules = agent.schedule.list()
print(f"Found {len(agent_schedules)} schedule(s)")
# Check runs for a specific schedule
for schedule in agent_schedules:
runs = agent.schedule.list_runs(schedule.id)
print(f"\nSchedule: {schedule.input}")
print(f"Runs: {len(runs)}")
# Display recent runs
for run in runs[-5:]: # Last 5 runs
print(f" - Run ID: {run.id}")
print(f" Status: {run.status}")
if run.status == "success":
result = run.get_result()
print(f" Result: {result}"){% endcode %}
Send a daily report every weekday at 9 AM:
{% code lineNumbers="true" %}
daily_report_schedule = ScheduleConfig(
minute="0",
hour="9",
day_of_month="*",
month="*",
day_of_week="0-4", # Monday to Friday
)
report_item = ScheduleItemConfig(
schedule_config=daily_report_schedule,
input="Generate and send the daily morning report"
){% endcode %}
Check data every hour during business hours:
{% code lineNumbers="true" %}
hourly_check_schedule = ScheduleConfig(
minute="0",
hour="9-17", # 9 AM to 5 PM
day_of_month="*",
month="*",
day_of_week="0-4", # Monday to Friday
)
check_item = ScheduleItemConfig(
schedule_config=hourly_check_schedule,
input="Check system status and alert if issues found"
){% endcode %}
Generate a weekly summary every Friday at 5 PM:
{% code lineNumbers="true" %}
weekly_summary_schedule = ScheduleConfig(
minute="0",
hour="17",
day_of_month="*",
month="*",
day_of_week="4", # Friday
)
summary_item = ScheduleItemConfig(
schedule_config=weekly_summary_schedule,
input="Generate weekly summary report"
){% endcode %}
Send a reminder on the first day of each month:
{% code lineNumbers="true" %}
monthly_reminder_schedule = ScheduleConfig(
minute="0",
hour="9",
day_of_month="1", # First day of month
month="*",
day_of_week="*",
)
reminder_item = ScheduleItemConfig(
schedule_config=monthly_reminder_schedule,
input="Send monthly reminder to review pending tasks"
){% endcode %}
Run a task every 15 minutes:
{% code lineNumbers="true" %}
frequent_check_schedule = ScheduleConfig(
minute="*/15", # Every 15 minutes
hour="*",
day_of_month="*",
month="*",
day_of_week="*",
)
frequent_item = ScheduleItemConfig(
schedule_config=frequent_check_schedule,
input="Check for urgent notifications"
){% endcode %}
Provide clear, actionable instructions in the schedule input:
{% code lineNumbers="true" %}
# Good: Specific and actionable
ScheduleItemConfig(
schedule_config=schedule,
input="Review unread emails from VIP customers and respond to urgent ones"
)
# Avoid: Vague or unclear
ScheduleItemConfig(
schedule_config=schedule,
input="Check emails"
){% endcode %}
Be aware of the time zone used by your deployment:
{% code lineNumbers="true" %}
# Document the time zone in comments
# Schedule runs at 9 AM UTC
morning_schedule = ScheduleConfig(
minute="0",
hour="9",
day_of_month="*",
month="*",
day_of_week="0-4",
){% endcode %}
Ensure schedules don't conflict or create excessive load:
{% code lineNumbers="true" %}
# Good: Staggered schedules
schedule_1 = ScheduleConfig(minute="0", hour="9", ...) # 9:00 AM
schedule_2 = ScheduleConfig(minute="30", hour="9", ...) # 9:30 AM
# Avoid: Multiple schedules at the same time
schedule_1 = ScheduleConfig(minute="0", hour="9", ...) # 9:00 AM
schedule_2 = ScheduleConfig(minute="0", hour="9", ...) # 9:00 AM (conflict){% endcode %}
Verify your cron expressions produce the expected schedule:
{% code lineNumbers="true" %}
schedule = ScheduleConfig(
minute="0",
hour="9",
day_of_month="*",
month="*",
day_of_week="0-4",
)
# Check the cron string
cron_string = schedule.to_cron_string()
print(f"Cron expression: {cron_string}")
# Output: "0 9 * * 0-4"{% endcode %}
Regularly check schedule runs to ensure they're executing as expected:
{% code lineNumbers="true" %}
# Check recent failed runs
failed_runs = agent.schedule.list_runs(schedule_id, status="failed")
if failed_runs:
print(f"Warning: {len(failed_runs)} failed runs detected"){% endcode %}
Group related schedules in a single Digital Employee:
{% code lineNumbers="true" %}
# Good: Related schedules for a single purpose
digital_employee = DigitalEmployee(
identity=identity,
schedules=[
morning_report_item, # Daily morning report
afternoon_check_item, # Afternoon status check
evening_summary_item, # Evening summary
],
)
# Avoid: Unrelated schedules in one employee
# Consider creating separate Digital Employees for different purposes{% endcode %}
Use detailed inputs to provide context:
{% code lineNumbers="true" %}
schedule_item = ScheduleItemConfig(
schedule_config=schedule,
input="""
Generate a daily report including:
1. Summary of completed tasks
2. Pending items requiring attention
3. Any system alerts or issues
4. Send the report to the team channel
"""
){% endcode %}
Schedules work seamlessly with other Digital Employee features:
{% code lineNumbers="true" %}
from digital_employee_core.connectors.mcps import google_mail_mcp
# Digital Employee with schedules and MCP tools
digital_employee = DigitalEmployee(
identity=identity,
schedules=[email_check_schedule_item],
mcps=[google_mail_mcp], # Can use email tools in scheduled tasks
configurations=configurations,
){% endcode %}
Problem: Schedule was created but tasks are not running.
Solution:
- Verify the Digital Employee is deployed:
digital_employee.deploy() - Check the cron expression is valid:
schedule.to_cron_string() - Confirm the schedule exists:
agent.schedule.list() - Check for failed runs:
agent.schedule.list_runs(schedule_id)
Problem: Schedule runs at unexpected times.
Solution:
- Verify time zone settings
- Double-check cron parameters (especially
day_of_weekwhere 0=Monday) - Test with
to_cron_string()to see the actual cron expression - Review the
next_run_timefield in the schedule object
Problem: Schedule triggers but execution fails.
Solution:
- Check run details:
run.get_result()for error messages - Verify the input instruction is clear and actionable
- Ensure required tools/MCPs are configured and accessible
- Review Digital Employee logs for detailed error information
Problem: Multiple schedules running simultaneously cause issues.
Solution:
- Stagger schedule times by adjusting minute/hour values
- Consider if schedules can be combined into a single task
- Monitor system resources and adjust frequency if needed
{% code lineNumbers="true" %}
from glaip_sdk.models.schedule import ScheduleConfig
schedule = ScheduleConfig(
minute: str, # "0-59", "*", "*/15", "0,30"
hour: str, # "0-23", "*", "9-17", "8,12,18"
day_of_month: str, # "1-31", "*", "1,15"
month: str, # "1-12", "*", "1-6"
day_of_week: str, # "0-6", "*", "0-4", "0,6"
)
# Convert to cron string
cron_string = schedule.to_cron_string(){% endcode %}
{% code lineNumbers="true" %}
from digital_employee_core.schedule import ScheduleItemConfig
schedule_item = ScheduleItemConfig(
schedule_config: ScheduleConfig, # The timing configuration
input: str, # The instruction to execute
){% endcode %}
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployee
digital_employee = DigitalEmployee(
identity: DigitalEmployeeIdentity,
schedules: list[ScheduleItemConfig] = [], # Optional list of schedules
mcps: list = [], # Optional MCP connectors
configurations: list = [], # Optional configurations
)
# Deploy to activate schedules
digital_employee.deploy()
# Get configured schedules
schedules = digital_employee.get_schedule(){% endcode %}
{% code lineNumbers="true" %}
from glaip_sdk import Client
client = Client()
agent = client.get_agent_by_id(agent_id)
# List all schedules
schedules = agent.schedule.list()
# Get runs for a schedule
runs = agent.schedule.list_runs(schedule_id)
# Access run details
for run in runs:
print(f"Status: {run.status}")
print(f"ID: {run.id}")
if run.status == "success":
result = run.get_result(){% endcode %}
For complete working examples, see:
- https://github.com/GDP-ADMIN/CATAPA-SDK/blob/main/python/digital-employee-core/examples/scheduler_usage.py - Comprehensive scheduler demonstration
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/scheduler-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Skills let a Digital Employee follow a reusable operating guide for a specific task or domain.
This repository follows the AIP skills configuration model. For the canonical guide, see the AIP Skills Guide.
There are two supported usage patterns demonstrated by the examples:
- Remote GitHub-based skills
- Local path-based skills
Use remote skills when you want a deployable Digital Employee that references skills stored in GitHub. Use local skills when you want to develop and test skill behavior locally without deploying.
A skill is an instruction package that gives the Digital Employee a narrowly scoped behavior. In practice, the examples in this repository show two ways to provide a skill:
- A GitHub URL pointing to a skill directory
- A local filesystem path loaded with
Skill.from_path(...)
You attach skills when constructing DigitalEmployee:
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployee
DigitalEmployee(
identity=identity,
skills=skills,
){% endcode %}
You can also add skills later using digital_employee.add_skills(...) before deployment or execution.
Remote GitHub skills
In examples/skills_example.py, the skill source is a GitHub URL:
{% code lineNumbers="true" %}
canvas_design_skill = "https://github.com/anthropics/skills/tree/main/skills/canvas-design"
return DigitalEmployee(identity=identity, skills=[canvas_design_skill]){% endcode %}
This is the right choice when:
- You want skills stored in a versioned repository
- You want the Digital Employee to be deployed and run remotely
- You want to share the same skill source across environments
Local path-based skills
In examples/local_skills/local_skills_example.py, the skill source is loaded from disk:
{% code lineNumbers="true" %}
from glaip_sdk.skills import Skill
local_skill = Skill.from_path(str(skill_path))
return DigitalEmployee(identity=identity, skills=[local_skill]){% endcode %}
This is the right choice when:
- You are developing a skill locally
- You want deterministic testing during development
- You do not want to deploy the skill first
The file examples/skills_example.py demonstrates the remote workflow.
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployee, DigitalEmployeeIdentity, DigitalEmployeeJob
job = DigitalEmployeeJob(
title="Skills-enabled Assistant - Example",
description="A digital employee using remote skills from GitHub",
instruction="You are a helpful assistant.",
)
identity = DigitalEmployeeIdentity(
name="skills_github_assistant",
email="skills.github@example.com",
job=job,
){% endcode %}
{% code lineNumbers="true" %}
canvas_design_skill = "https://github.com/anthropics/skills/tree/main/skills/canvas-design"
github_de = DigitalEmployee(identity=identity, skills=[canvas_design_skill]){% endcode %}
{% code lineNumbers="true" %}
github_de.deploy()
message = (
"Create a canvas design with a blue background and the text 'Hello, World!' "
"in white, do not ask any follow-up questions, just create the design."
)
result = github_de.run(message=message){% endcode %}
- The Digital Employee accepts a GitHub URL as a skill source
- Remote skills are used in the normal deployed workflow
deploy()is called beforerun(...)
The example notes that public repositories work directly. For private repositories, you must provide one of these environment variables:
GITHUB_PERSONAL_ACCESS_TOKENGITHUB_TOKENGH_TOKEN
The directory examples/local_skills/ demonstrates the local workflow.
Make a file in ./.agents/skills/haiku-standup/SKILL.md with the following content:
{% code lineNumbers="true" %}
---
name: haiku-standup
description: >
Format daily standup updates into the team's custom standup template.
Use when the user mentions "standup", "daily update", "what I did yesterday",
or asks to format their work status.
---
# Haiku Standup Formatter
Format every standup update using the exact structure below. Never skip sections.
Never reorder them. Always generate the haiku — do not ask the user to write one.
## Output Template
```text
📋 STANDUP — [TODAY'S DATE in YYYY-MM-DD]
🎋 [A haiku summarizing the update — must be valid 5-7-5 syllable structure]
🔥 BLOCKER [PRIORITY-CODE]
[Blocker description — what it is and what it's blocking]
(If no blockers, write: "☁️ ALL CLEAR — No blockers today")
✅ DONE
- [Completed item 1]
- [Completed item 2]
🎯 TODAY
- [Planned item 1]
- [Planned item 2]
Vibe Check: [MOON-RATING] ([N]/5)
```{% endcode %}
{% code lineNumbers="true" %}
from pathlib import Path
skill_path = Path(__file__).parent / ".agents/skills/haiku-standup"{% endcode %}
{% code lineNumbers="true" %}
from glaip_sdk.skills import Skill
local_skill = Skill.from_path(str(skill_path)){% endcode %}
{% code lineNumbers="true" %}
job = DigitalEmployeeJob(
title="Local Skills Assistant",
description="A digital employee using local skills for deterministic development",
instruction="You are a helpful assistant. Use the attached local skill as your operating guide.",
)
identity = DigitalEmployeeIdentity(
name="skills_local_assistant",
email="skills.local@example.com",
job=job,
)
local_de = DigitalEmployee(identity=identity, skills=[local_skill]){% endcode %}
{% code lineNumbers="true" %}
message = (
"Standup: Yesterday I migrated the user table to the new schema and pair-programmed with Alex on the search "
"feature. Today I'm writing tests for the migration. I'm stuck waiting on QA to finish their test plan."
)
result = local_de.run(message=message, local=True){% endcode %}
- A local skill is loaded from a filesystem path
- The skill folder is expected to exist before execution
- Path-based skills are intended for local execution only
- Local skills should be run with
run(..., local=True) - The example does not call
deploy()
- You want a deployable Digital Employee
- Your skill definitions are stored in GitHub
- You want centralized version control for skill content
- You are building a remotely hosted assistant workflow
- You are iterating on skill content locally
- You want quick testing without deployment
- You need local-only experimentation or deterministic development loops
- You already have a local skill folder with a valid
SKILL.md
Write each skill for a specific job, such as copywriting or standup formatting, rather than combining many unrelated behaviors into one skill.
Follow the examples:
- Remote GitHub skill example: call
deploy()beforerun(...) - Local path-based skill example: skip deploy and call
run(..., local=True)
The local example checks that the path exists before creating the employee. This is a good pattern when developing local skills.
A strong skill file should clearly define:
- When the skill applies
- The expected output structure
- Rules and constraints
- What the model must infer versus what it must ask about
Problem: The local skill does not load.
Solution:
- Verify the directory path is correct
- Verify
SKILL.mdexists at the root of the skill directory - Verify you are calling
Skill.from_path(str(skill_path))
Problem: The output ignores the intended format.
Solution:
- Make the instructions in
SKILL.mdmore explicit - Add stricter output templates and rules
- Ensure the employee instruction does not conflict with the skill behavior
Problem: The Digital Employee cannot use the GitHub-based skill.
Solution:
- Verify the GitHub URL points to the correct skill directory
- If the repository is private, set
GITHUB_PERSONAL_ACCESS_TOKEN,GITHUB_TOKEN, orGH_TOKEN - Ensure you call
deploy()beforerun(...)
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/skills-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
A Digital Employee can be composed of a coordinator agent plus one or more sub-agents. Sub-agents are attached as nested agents on the underlying glaip_sdk.Agent instance.
In this repository, see examples/configuration_propagation_example.py for a working example (including config propagation).
A sub-agent is a glaip_sdk.Agent instance attached under the coordinator agent. At deploy-time, DigitalEmployee builds a single root Agent and passes sub-agents into the Agent(..., agents=[...]) field.
You attach sub-agents by providing them to DigitalEmployee:
DigitalEmployee(sub_agents=[...])(recommended)digital_employee.add_sub_agents([...])
When the Digital Employee is deployed, it will process sub_agents and automatically propagate tool/MCP configs from the parent to sub-agents when:
- The sub-agent uses a tool/MCP.
- The sub-agent does not already define a config for that tool/MCP.
Notes:
- Existing sub-agent configs take precedence over propagated configs.
- Propagation is recursive: nested sub-agents (sub-agents of sub-agents) are processed as well.
- Currently only
glaip_sdk.Agentis supported as a sub-agent type; unsupported types are skipped with a warning.
from digital_employee_core import DigitalEmployeeIdentity, DigitalEmployeeJob
job = DigitalEmployeeJob(
title="Coordinator",
description="Delegates tasks to specialists",
instruction="You are the coordinator. Delegate tasks to your sub-agents.",
)
identity = DigitalEmployeeIdentity(name="Coordinator", email="coordinator@example.com", job=job)from glaip_sdk import Agent
reminder_agent = Agent(
name="ReminderAgent",
instruction="You are a reminder specialist.",
)from digital_employee_core import DigitalEmployee
digital_employee = DigitalEmployee(
identity=identity,
sub_agents=[reminder_agent],
)
digital_employee.deploy()- Add or remove sub-agents before deploy; changes after deploy require re-deploying to update the agent graph.
- Prefer stable, unique
Agent.namevalues (removal uses name matching). - If sub-agents use MCPs/tools that require configs, prefer putting shared configs on the parent
DigitalEmployee.configurationsand rely on propagation; override only when a sub-agent needs different settings.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/sub-agents-configuration.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Digital Employee Core handles user information in two different ways:
- User-scoped memory uses
memory_user_idto isolate remembered facts per user. - User-authenticated tools use delegated user tokens so external systems can authorize actions as the current user.
These two mechanisms are related, but they solve different problems. memory_user_id controls memory isolation. Delegation tokens control what a tool is allowed to do on behalf of the user.
For the GL AIP delegation flow, see:
- Delegate to Agent for the delegation token object and scope structure.
- Validate Delegation Token for how receiving services validate delegation tokens and enforce scopes.
For GL Connectors integration setup, see Integration Setup.
memory_user_id is the stable user identifier used by the memory provider.
- Same
memory_user_id+ same agent => the agent can recall that user's previous facts. - Different
memory_user_id+ same agent => memory stays isolated. - It should be a stable internal user ID. Avoid PII such as email addresses unless required.
See also: Memory Configuration.
user_authentication is an opt-in flag in a tool config. When it is enabled, GL AIP knows that the dependency needs a delegated user token.
Use it for tools that call user-scoped APIs, such as calendars, mail, HR self-service, finance self-service, GL Connectors, or other systems where permissions depend on the current user.
Delegated tokens are passed at run time, not written into prompts. GL AIP validates the incoming delegation token, resolves which downstream token is needed for each user-authenticated dependency, then exposes that token to tools through runtime metadata.
For example, a GL Connectors-enabled dependency receives gl_connectors_token in RunnableConfig.metadata. For local or manual runs, you can pass the token explicitly to run():
result = digital_employee.run(
message="Show my pending requests",
gl_connectors_token=os.getenv("GL_CONNECTORS_TOKEN"),
)The token name depends on the downstream integration. For GL Connectors, use gl_connectors_token.
Tools should not parse the original GL IAM delegation token object directly. Treat GL AIP as the boundary that:
- Receives the delegation token object described in Delegate to Agent.
- Validates the delegation token as described in Validate Delegation Token.
- Looks up user-authenticated dependencies and attaches the appropriate integration token, such as
gl_connectors_token, to tool runtime metadata.
In custom tools, read only the integration-specific token from RunnableConfig.metadata and use it to authenticate with the downstream connector.
When using GL Connectors, configure the connector integration first. See GL Connectors Integration Setup.
Digital Employee tools can receive user and conversation context through RunnableConfig.metadata. This context can be used by the tools to act on behalf of human (OBOH).
The runtime parameter contract is sent by GLChat to AIP from the AIP execution strategy.
The main chat-message path populates this context from the Agent message processor. Pipeline-based runs may provide context through the Pipeline service.
| Parameter | Location | Purpose |
|---|---|---|
gl_connectors_token |
RunnableConfig.metadata.gl_connectors_token |
Delegated connector token for the current user. |
user_id |
RunnableConfig.metadata.user_id |
Current user identifier. |
tenant_id |
RunnableConfig.metadata.tenant_id |
Current tenant context. |
conversation_id |
RunnableConfig.metadata.conversation_id |
Conversation associated with the run. |
message_id |
RunnableConfig.metadata.message_id |
User message associated with the run. |
email |
RunnableConfig.metadata.email |
Current user email or username fallback. |
organization_id |
RunnableConfig.metadata.organization_id |
Organization context. |
chatbot_id |
RunnableConfig.metadata.chatbot_id |
Chatbot or assistant identifier. |
agent |
RunnableConfig.metadata.agent |
Agent-specific metadata, when provided. |
Example usage in a custom tool:
def _run(self, config: RunnableConfig = None, **kwargs):
metadata = ((config or {}).get("metadata") or {})
gl_connectors_token = metadata.get("gl_connectors_token")
user_id = metadata.get("user_id")
tenant_id = metadata.get("tenant_id")
conversation_id = metadata.get("conversation_id")
message_id = metadata.get("message_id")
email = metadata.get("email")
organization_id = metadata.get("organization_id")
chatbot_id = metadata.get("chatbot_id")
agent_metadata = metadata.get("agent")Do not store these values in tool configuration or prompts. Treat them as runtime context for the current Digital Employee run.
Enable memory in agent_config, then pass memory_user_id on each run() or arun() call.
{% code lineNumbers="true" %}
from digital_employee_core import DigitalEmployee, DigitalEmployeeIdentity, DigitalEmployeeJob
from digital_employee_core.configuration.agent_configuration import AgentConfigKeys, MemoryProvider
job = DigitalEmployeeJob(
title="Memory-Enabled Assistant",
description="A digital employee that can remember user-specific facts across calls",
instruction="When the user tells you a personal preference or fact, remember it for future conversation.",
)
identity = DigitalEmployeeIdentity(name="memory_assistant", email="memory.assistant@example.com", job=job)
digital_employee = DigitalEmployee(
identity=identity,
agent_config={AgentConfigKeys.MEMORY: MemoryProvider.MEM0},
)
digital_employee.deploy()
memory_user_id = "user-123"
digital_employee.run(
message="My preferred report format is a short bullet summary. Please remember this.",
memory_user_id=memory_user_id,
)
digital_employee.run(
message="How should you format my reports?",
memory_user_id=memory_user_id,
){% endcode %}
Note: If memory is enabled,
memory_user_idis required. Digital Employee Core raises an error whenrun()orarun()is called without it.
Custom tools should treat delegated user tokens as runtime credentials. Do not store them in static config and do not include them in prompts.
Digital Employee Core custom tools follow the LangChain BaseTool pattern:
- Define an input schema with Pydantic.
- Define a tool config schema with Pydantic.
- Set
tool_config_schemaon the tool. - Read static config with
self.get_tool_config(config). - Read delegated user tokens from
RunnableConfig.metadata, such asconfig.get("metadata").get("gl_connectors_token").
This example creates a custom user_profile_tool that calls GL Connectors to fetch the current user's profile. The API key is service-level authentication. The gl_connectors_token is delegated user authentication for the current run.
Before using this pattern, ensure the target connector integration is configured in GL Connectors. See Integration Setup.
{% code lineNumbers="true" %}
import json
import requests
from typing import Any
from gl_connectors_sdk import GLConnectors
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import BaseTool
from pydantic import BaseModel, Field
REQUEST_TIMEOUT_SECONDS = 30
class UserProfileToolInput(BaseModel):
"""Input schema for user profile tool."""
include_contact: bool = Field(default=False, description="Whether to include contact fields in the response.")
class UserProfileToolConfig(BaseModel):
"""Configuration schema for user profile tool."""
user_api_base_url: str = Field(description="The base URL for the downstream user profile API.")
gl_connectors_api_base_url: str = Field(description="The base URL for the GL Connectors API.")
gl_connectors_api_key: str = Field(description="The API key for authenticating with the GL Connectors API.")
user_authentication: bool = Field(description="Whether this tool requires delegated user authentication.", default=True)
class UserProfileTool(BaseTool):
"""Tool for reading the current user's profile through GL Connectors."""
name: str = "user_profile_tool"
description: str = "Read the current user's profile information."
args_schema: type[BaseModel] = UserProfileToolInput
tool_config_schema: type[BaseModel] = UserProfileToolConfig
def _run(self, include_contact: bool = False, config: RunnableConfig = None, **_kwargs: Any) -> str:
"""Read the current user's profile."""
tool_config = self.get_tool_config(config)
gl_connectors_token = ((config or {}).get("metadata") or {}).get("gl_connectors_token")
if not gl_connectors_token:
return "Error: gl_connectors_token is required for this user-authenticated tool."
try:
access_token = self._get_access_token_from_gl_connectors(tool_config, gl_connectors_token)
headers = {"Authorization": f"Bearer {access_token}"}
params = {"include_contact": include_contact}
base_url = tool_config.user_api_base_url.rstrip("/")
url = f"{base_url}/user/profile"
response = requests.get(url, headers=headers, params=params, timeout=REQUEST_TIMEOUT_SECONDS)
response.raise_for_status()
return response.text
except requests.HTTPError as e:
return f"Failed to read user profile. Status code: {e.response.status_code}, Response: {e.response.text}"
except Exception as e:
return f"Error reading user profile: {str(e)}"
def _get_access_token_from_gl_connectors(
self,
tool_config: UserProfileToolConfig,
gl_connectors_token: str,
) -> str:
"""Exchange the delegated GL Connectors token for downstream auth info."""
connector = GLConnectors(
api_base_url=tool_config.gl_connectors_api_base_url,
api_key=tool_config.gl_connectors_api_key,
)
# Check which integration belongs to this delegated user.
user_info = connector.get_user_info(gl_connectors_token)
user_identifier = next(
integration.user_identifier
for integration in user_info.integrations
if integration.connector == "user-profile"
)
# GL Connectors returns integration-specific auth information as auth_string.
integration_info = connector.get_integration("user-profile", gl_connectors_token, user_identifier)
auth_string = integration_info.get("auth_string")
if not auth_string:
raise ValueError("auth_string is missing or empty")
return json.loads(auth_string)["access_token"]{% endcode %}
Set user_authentication: true in the tool config template. This tells GL AIP that the tool needs delegated user authentication.
config_templates/tool_configs.yaml:
user_profile_tool:
user_api_base_url: "${USER_API_BASE_URL}"
gl_connectors_api_base_url: "${GL_CONNECTORS_API_BASE_URL}"
gl_connectors_api_key: "${GL_CONNECTORS_API_KEY}"
user_authentication: trueThe config key must match the tool name:
class UserProfileTool(BaseTool):
name: str = "user_profile_tool"The delegated token is passed to digital_employee.run() and then forwarded to the tool call through RunnableConfig.metadata. In the custom tool, read it from config.get("metadata").get("gl_connectors_token").
def _run(self, include_contact: bool = False, config: RunnableConfig = None, **_kwargs: Any) -> str:
tool_config = self.get_tool_config(config)
gl_connectors_token = ((config or {}).get("metadata") or {}).get("gl_connectors_token")
if not gl_connectors_token:
return "Error: gl_connectors_token is required for this user-authenticated tool."
access_token = self._get_access_token_from_gl_connectors(tool_config, gl_connectors_token)
headers = {
"Authorization": f"Bearer {access_token}",
}Use the GL Connectors token to retrieve the integration auth_string, then parse the auth string for the downstream API credential. The connector and its integration must already be set up in GL Connectors. See Integration Setup.
def _get_access_token_from_gl_connectors(
self,
tool_config: UserProfileToolConfig,
gl_connectors_token: str,
) -> str:
connector = GLConnectors(
api_base_url=tool_config.gl_connectors_api_base_url,
api_key=tool_config.gl_connectors_api_key,
)
user_info = connector.get_user_info(gl_connectors_token)
user_identifier = next(
integration.user_identifier
for integration in user_info.integrations
if integration.connector == "user-profile"
)
integration_info = connector.get_integration("user-profile", gl_connectors_token, user_identifier)
auth_string = integration_info.get("auth_string")
if not auth_string:
raise ValueError("auth_string is missing or empty")
return json.loads(auth_string)["access_token"]In this pattern:
gl_connectors_api_keyauthenticates the tool to GL Connectors.gl_connectors_tokenidentifies the current delegated user in GL Connectors.auth_stringcontains the downstream integration credential for that delegated user.user_authentication: truesignals that GL AIP should delegate the appropriate token to the tool.
For GL Connectors setup details, see GL Connectors Integration Setup.
Digital Employee Core propagates tool configs from the parent Digital Employee to sub-agents by dependency name.
This means a parent can define user_authentication: true once for a shared tool, and a sub-agent that uses the same dependency can receive the config automatically.
{% code lineNumbers="true" %}
digital_employee = DigitalEmployee(
identity=identity,
tools=[Tool.from_langchain(UserProfileTool)],
sub_agents=[profile_helper_agent],
configurations=configurations,
)
digital_employee.deploy(){% endcode %}
If the sub-agent defines its own config for the same tool, the sub-agent config takes precedence.
See also: Sub-Agents Configuration.
Wrap the custom tool with Tool.from_langchain() and add a config loader for the tool config template.
For deployed GL AIP runs, AIP validates the delegation token and attaches integration-specific tokens to runtime metadata. For local or manual runs, pass the delegated token explicitly in run().
{% code lineNumbers="true" %}
import os
from pathlib import Path
from dotenv import load_dotenv
from glaip_sdk import MCP, Agent, Tool
from digital_employee_core import (
DEFAULT_MODEL_NAME,
ConfigTemplateLoader,
DigitalEmployee,
DigitalEmployeeConfiguration,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
)
from my_project.tools.user_profile_tool import UserProfileTool
load_dotenv()
class MyDigitalEmployee(DigitalEmployee):
"""Digital Employee with custom tool config templates."""
def __init__(
self,
identity: DigitalEmployeeIdentity,
tools: list[Tool] | None = None,
sub_agents: list[Agent] | None = None,
mcps: list[MCP] | None = None,
configurations: list[DigitalEmployeeConfiguration] | None = None,
model: str | None = DEFAULT_MODEL_NAME,
):
super().__init__(identity, tools, sub_agents, mcps, configurations, model)
config_dir = Path(__file__).parent / "config_templates"
self.add_config_loader(ConfigTemplateLoader(template_dir=config_dir))
identity = DigitalEmployeeIdentity(
name="profile_assistant",
email="profile.assistant@example.com",
job=DigitalEmployeeJob(
title="Profile Assistant",
description="Helps users retrieve their own profile information",
instruction="Use the user profile tool when the user asks about their own profile.",
),
)
configurations = [
DigitalEmployeeConfiguration(key="USER_API_BASE_URL", value=os.getenv("USER_API_BASE_URL", "")),
DigitalEmployeeConfiguration(key="GL_CONNECTORS_API_BASE_URL", value=os.getenv("GL_CONNECTORS_API_BASE_URL", "")),
DigitalEmployeeConfiguration(key="GL_CONNECTORS_API_KEY", value=os.getenv("GL_CONNECTORS_API_KEY", "")),
]
# GL_CONNECTORS_TOKEN is a delegated user token for the current run.
# See the GL AIP Delegate to Agent guide for how to obtain it.
gl_connectors_token = os.getenv("GL_CONNECTORS_TOKEN")
digital_employee = MyDigitalEmployee(
identity=identity,
tools=[Tool.from_langchain(UserProfileTool)],
configurations=configurations,
)
digital_employee.deploy()
result = digital_employee.run(
message="Show my profile information",
gl_connectors_token=gl_connectors_token,
){% endcode %}
- Do not put tokens in prompts. Pass delegated tokens as
run()/arun()keyword arguments only for local/manual runs; in deployed GL AIP runs, read the resolved token fromRunnableConfig.metadata. - Do not parse GL IAM delegation tokens in custom tools. Use GL AIP for delegation-token validation and only consume the integration-specific token exposed to the tool.
- Use
memory_user_idonly for memory scoping. Do not use it as an authorization token. - Enable
user_authenticationonly when needed. Tools that only use service credentials do not need delegated user tokens. - Keep service credentials in configuration. Use
DigitalEmployeeConfiguration, config templates or environment variables. - Propagate configs intentionally. Shared parent configs are convenient for sub-agents, but sub-agent-specific configs should be explicit when permissions differ.
- Validate original delegation tokens at service boundaries. If a receiving service handles the original GL IAM delegation token directly, validate it according to Validate Delegation Token.
Check that every run() or arun() call includes a non-empty memory_user_id.
digital_employee.run(
message="What did I tell you earlier?",
memory_user_id="user-123",
)Check that:
- The tool config includes
user_authentication: true. - The run call passes the integration token, for example
gl_connectors_token. - The token is available in the environment when running locally.
export GL_CONNECTORS_TOKEN="<delegated_token>"This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/advanced-examples/user-information-in-digital-employee-runs.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
The Digital Employee system is an AI-powered automation platform built upon the AI Agent Platform (AIP). It enables autonomous workflow execution through intelligent agents. AIP manages agent operations, configurations, and orchestration of these digital employees.
The current implementation features a specific digital employee: the HR Recruiter, designed to streamline recruitment pipeline operations.
{% hint style="info" %} To learn more about the digital employee, please refer to the Digital Employee GitBook. {% endhint %}
The architecture consists of three primary layers: the User Interaction Layer, the Digital Employee instance, and the underlying AI Agent Platform (AIP).
This layer facilitates communication between human users and the digital employee.
- Claudia UI: A manual interface that allows users to provide direct instructions and interact via prompts.
- WhatsApp: A mobile interface enabling direct user instructions and prompt-based interactions, where users can send prompts and receive answers directly through the messaging app.
The digital employee is the core component and contains multiple agents with diverse capabilities.
- Function: Operates as an intelligent agent utilizing specialized capabilities to execute tasks.
- Current Application: The HR Recruiter digital employee processes candidates throughout the entire recruitment lifecycle.
AIP is the foundation for the system.
- Role: Manages creation, configuration, and orchestration of all agents and digital employees.
- Environment: All agents are created and maintained within the AIP environment.
The Digital Employee application uses Model Context Protocol (MCP) services to enable agents to interact securely with external systems and services. MCPs are managed through the AIP and use HTTP transport.
The system integrates with Google Workspace services using API Key authentication (X-API-Key header).
| MCP Service | Description | Purpose |
|---|---|---|
| Google Calendar | Calendar Operations | Manages calendar events, schedules interviews, and coordinates meetings. |
| Google Docs | Document Operations | Handles document creation, editing, and management for recruitment workflows. |
| Google Drive | File Operations | Enables storage, retrieval, and management of recruitment documents. |
| Google Mail | Email Operations | Facilitates sending candidate communications and managing recruitment emails. |
| Google Sheets | Spreadsheet Operations | Manages recruitment data tracking and reporting via spreadsheets. |
In addition to Google Workspace, the system connects to specialized enterprise platforms.
- Description: Connects to CATAPA's digital_employee PostgreSQL database.
- Transport: HTTP.
- Authentication: Custom Headers (Bearer token, X-Api-Key, and X-Bosa-Integration headers).
- Purpose: Provides direct access to query and manage recruitment data stored in CATAPA's database system.
- Description: Handles operations for the Evalground platform.
- Transport: HTTP.
- Authentication: Bearer Token (Authorization header).
- Purpose: Enables candidate evaluation and assessment operations, specifically for practical test processes.
Digital employees utilize "Tools" to interact with applications outside the scope of MCPs (for example, the CATAPA API). Tools are categorized into two types: Built-in Tools and User-Defined Tools.
Generic, pre-configured collections provided by the AI Agent Platform. Designed for ease of use without coding. These are some of the built-in tools:
date_range_tool: Utility for handling date ranges.cv_extractor_tool: Utility for extracting information from Curricula Vitae.time_tool: Utility for time management.
Custom tools created to handle specific tasks not covered by generic tools. Implemented as single-file Python code. These are some of the user-defined tools:
get_employee_info_tool: Retrieves specific employee information.update_candidate_phase_tool: Updates the recruitment phase of a candidate.detect_sister_company_tool: Logic to detect associated sister companies.
{% stepper %} {% step %} Automation
Significantly reduces manual intervention by enabling scheduled operations. {% endstep %}
{% step %} Integration
Offers seamless connectivity with Google Workspace and other enterprise systems. {% endstep %}
{% step %} Flexibility
Features an extensible architecture that supports both standard (Built-in) and custom (User-Defined) tools. {% endstep %}
{% step %} Scalability
The MCP-based architecture allows for the easy addition of new services and capabilities as needs evolve. {% endstep %} {% endstepper %}
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/digital-employee-architecture.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
Digital Employee Core is the SDK designed to make building digital employees easier. The SDK contains common connectors, agents, and functions to build or run a digital employee. One of the components is the digital employee class itself.
When creating a digital employee, you will use a pipeline (GL Pipeline) as the foundation. For each step in the pipeline, we can customize the step as desired. We can create many implementations for each step. For example, we can execute an agent in that step (Agent as step), execute a pipeline in that step (Pipeline as step), or implement another simple logic (Non-agent and non-pipeline as step).
- Efficiency: Fewer LLM calls — reduced operational cost
- Maintainability: Modular step design — independent changes without cascading effects
- Reproducibility: Deterministic execution — same inputs produce same outputs
- Responsiveness: Fewer LLM calls — reduced end-to-end latency
- Testability: Isolated step boundaries — independent testing of each step
Previously, digital employees ran on an AIP server, but now, by default, each digital employee should have its own server to run the digital employee pipeline so that it doesn't have a dependency on the AIP server.
Since digital employees now have a pipeline component, it is beneficial to run it in the digital employee application itself for scalability reasons, and there is no equivalent AIP runner for the GL pipeline.
The digital employee application will be integrated with GLChat so that GLChat can execute digital employees with the GL pipeline as a foundation instead of executing agents on the AIP server. When the digital employee pipeline consists of a step related to an agent, the execution of that agent also doesn't run on the AIP server; it will run on the digital employee server itself.
The digital employee runner should be isolated from the main digital employee application so that it will not affect the main application when there are errors or any risks in the runner. We can call this running in a sandbox.
We recommend reusing the digital employee application for multiple digital employee instances. For example, CATAPA can create one digital employee application that consists of two digital employee instances: digital employee payroll officer and digital employee payroll analyst. Choosing whether to combine a new digital employee with an existing digital employee application or create a new digital employee application currently depends on your use case and requirements, especially from a scalability perspective.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/digital-employee-architecture/digital-employee-detailed-block-diagram.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
A Digital Employee (DE) is built on top of GDP Labs’ core technologies—AI Agent Package (AIP), GL Connectors, and GLChat—which together provide the foundation for an intelligent, integrated, and scalable knowledge worker. AIP supplies the agentic “brain,” enabling the DE to reason, plan, and execute tasks. GL Connectors serve as the integration layer, securely linking the DE to the tools, data sources, and enterprise systems it needs to do real work. GLChat delivers the conversational interface that allows the DE to collaborate naturally with human teams, making it easy to assign work, review outcomes, and keep operations transparent.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/digital-employee-architecture/tech-stack-overview.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
This quickstart guide walks you through a simple setup to create a digital employee in just a few minutes.
To follow this example, you will need to:
- Install the
digital-employee-corepackage - If you are planning to deploy and/or the digital employee in a remote AIP server, set the
AIP_API_URLandAIP_API_KEYenvironment variables in your terminal or in a.envfile
{% hint style="info" %}
You should provide AIP_API_URL and AIP_API_KEY, because the digital employee depends on these two environment variables when you are using a remote AIP server.
To get the values for these environment variables, please ask your team which AIP server has been deployed, and then obtain the AIP key. For example, in CATAPA, we use AIP_API_URL=https://aip-dev.catapa.com/ as our AIP development environment.
You can set the environment variables in your terminal by using:
export AIP_API_URL=<AIP_API_URL>
export AIP_API_KEY=<AIP_API_KEY>
{% endhint %}
- If you are planning to run the digital employee locally, set the
OPENAI_API_KEYenvironment variables in your terminal or in a.envfile
{% hint style="info" %}
Similar to AIP_API_URL and AIP_API_KEY, you can set OPENAI_API_KEY environment variable by using:
export OPENAI_API_KEY=<OPENAI_API_KEY>{% endhint %}
Start by creating a simple digital employee that can interact with Gmail. This agent will use OpenAI GPT-5.1 as its language model, an MCP setup, and a simple prompt to guide its behavior.
{% code lineNumbers="true" %}
from digital_employee_core import (
DigitalEmployee,
DigitalEmployeeConfiguration,
DigitalEmployeeIdentity,
DigitalEmployeeJob,
)
from digital_employee_core.connectors.mcps import google_mail_mcp{% endcode %}
{% code lineNumbers="true" %}
# Identity and configuration
job = DigitalEmployeeJob(
title="Digital Assistant",
description="A helpful digital employee assistant",
instruction="You are a helpful digital employee assistant. When asked to send a welcome email, craft a warm, professional message that introduces the team and includes helpful resources. Do not ask for clarification, just proceed with sending the email.",
)
identity = DigitalEmployeeIdentity(name="Claudia", email="claudia@example.com", job=job)
configurations = [
DigitalEmployeeConfiguration(key="GOOGLE_MAIL_MCP_URL", value="https://api.bosa.id/google_mail/mcp"),
DigitalEmployeeConfiguration(key="GOOGLE_MCP_X_API_KEY", value="[gl-connectors-x-api-key]"),
]
# Initialize digital employee
digital_employee = DigitalEmployee(identity=identity, configurations=configurations, mcps=[google_mail_mcp]){% endcode %}
{% code lineNumbers="true" %}
# Run the digital employee locally using a prompt
result = digital_employee.run(
message="Send a welcome email to [your-email] welcoming them to the team. Include a warm greeting, mention that you're excited to have them on board, and let them know you're here to help with any questions.",
local=True
)
print(result){% endcode %}
{% code lineNumbers="true" %}
# Deploy the digital employee to AIP server
digital_employee.deploy()
# Run the deployed digital employee using a prompt
result = digital_employee.run(message="Send a welcome email to [your-email] welcoming them to the team. Include a warm greeting, mention that you're excited to have them on board, and let them know you're here to help with any questions.")
print(result){% endcode %}
{% hint style="info" %} Before running the sample code, replace the following placeholders:
- Replace
[your-email]with your email address. - Replace
[gl-connectors-x-api-key]with x-api-key from GL Connectors. See below for one way to do it. - (Optional) Replace
GOOGLE_MAIL_MCP_URLif you are using a different GL Connectors server instance.
🔑 Get GL Connectors x-api-key
- Open https://api.bosa.id/console, then sign in.
- In the Credentials section, expand the x-api-key panel and click Copy combined value button. Paste this value to replace
[gl-connectors-x-api-key].
- If your Gmail account has not been integrated yet, continue with the steps below.
- Under Available Modules section, find the Google_mail integration and click Add New Integration button.
- An authorization URL will appear. Click or copy the URL, then authenticate using your Gmail account.
- Below is an example of a successfully integrated Gmail account.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/getting-started-example.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
To install the digital employee core package:
{% tabs %} {% tab title="pip" %}
pip install digital-employee-core{% endtab %}
{% tab title="poetry" %}
poetry add digital-employee-core{% endtab %}
{% tab title="uv" %}
uv add digital-employee-core{% endtab %} {% endtabs %}
Please ensure you have installed pip, poetry, or uv before installing the digital employee core package.
{% hint style="info" %} You can check the PyPI resource: Digital Employee Core PyPI. {% endhint %}
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/install-and-configure.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending
.mdto page URLs; this page is available as Markdown.
To support multi-tenancy, we created one digital employee per each tenant and can customize the prompt configuration, tool configuration, and MCP configuration at creation or runtime from each tenant. This approach allows us to make our digital employee generic.
Prompt configuration means we can configure the placeholders in the agent prompt (instruction), because sometimes we need to add placeholders to our prompt.
Here is an example of a prompt (instruction) with placeholders:
**A3.2.6 Find matched experience**
- Check if detected_sister_companies list is not empty.
- If there is at least one match in detected_sister_companies:
- **A3.2.6.1** Send confirmation email (HTML) via MCP Gmail:
- Use `google_mail_send_email` tool
- To: candidate email; CC: {sister_company_email_cc}// Some code
{sister_company_email_cc} is the placeholder that needs to be replaced at runtime.
Tool configuration is the configuration for the tool that will be consumed by the agent. Agent tools need an input to run the tool. For example, the audio_transcription tool needs a Prosa API key. In this case, we can configure which API key we will use for a specific tenant.
MCP configuration is similar to tool configuration, but this works for MCP. For example, MCP Google Mail needs a BOSA API key to be able to send emails to specific recipients. We can configure that key for a specific tenant.
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.
Perform an HTTP GET request on the current page URL with the ask query parameter, and the optional goal query parameter:
GET https://gdplabs.gitbook.io/catapa/developer-documentation/digital-employee/multi-tenant.md?ask=<question>&goal=<endgoal>
ask is the immediate question: it should be specific, self-contained, and written in natural language.
goal is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.
Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.