Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jamesjfoong/e507f07372bd8f4552d6134f7fa880b7 to your computer and use it in GitHub Desktop.

Select an option

Save jamesjfoong/e507f07372bd8f4552d6134f7fa880b7 to your computer and use it in GitHub Desktop.
GDP Labs AI Stack Field Guide + full CATAPA DE GitBook v2 (DE Core, GLAIP TUI, Claudia)

GDP Labs AI Stack Field Guide

A concise map of the moving pieces: GL SDK / GLAIP, GLAIR Connectors, and CATAPA Digital Employee.

1. Ecosystem map

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
Loading

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.

2. GL SDK / GLAIP

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_token is forwarded to /agents/{agent_id}/run and used for GL Connectors pre-checks.
  • During local make run it may not reach custom tools; add env fallback (GL_CONNECTORS_TOKEN) in the tool.

3. GLAIR Connectors

GLAIR Connectors is an OAuth gateway and tool catalog for external services (Google, GitHub, Slack, SQL, etc.).

Three ways to use it

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

API surface (admin/registry)

Base URLs:

  • https://connectors.glair.ai — common public API
  • https://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 / update
  • POST /users — create user
  • POST /auth/tokens — login, get user token
  • DELETE /auth/tokens — logout
  • GET /users/me
  • GET /connectors/{name}/auth-schema — auth config shape
  • POST /connectors/{name}/integrations — create integration
  • GET|POST|DELETE /connectors/{name}/integrations/{user_identifier}
  • GET /connectors/{name}/success-authorize-callback — OAuth callback
  • GET /catalog/mcp — list MCP servers
  • GET /catalog/tools — list native tools
  • GET /skills/list — curated skills registry
  • GET /skills/openclaw — UGC skills
  • GET /api/skills — skill details list
  • POST /api/skills — register a new skill
  • POST /api/skills/tenants — attach skill to tenant
  • GET /api/skills/tenants/{client_id}
  • PUT|GET|DELETE /api/skills/{skill_id}

SDK fluent shape

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()
)

MCP server pattern

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"]}
}

4. CATAPA Digital Employee

A Digital Employee (DE) is an LLM-driven agent inside CATAPA that answers domain-specific requests and performs workflows.

Topology

  • 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.

Build workflow (de-build-de-catapa)

Source of truth repo: https://github.com/GDP-ADMIN/prompt-template/tree/main/project-wide/digital-employee

Steps:

  1. Prepare PRD.
  2. Read de-build-de-catapa/README.md and AGENTS.md.
  3. Fill <REPLACE: ...> placeholders in step prompts.
  4. step-1-scaffolding.sh
  5. step-2-architecture-specification.md
  6. Review architecture (tools, MCPs, sub-agents, missing/unnecessary parts).
  7. step-3-implementation-specification.md
  8. Review spec (no invented endpoints, no invented query syntax, clear env vars, good errors).
  9. step-4-implementation.md
  10. Configure .env.
  11. make run MESSAGE="...".
  12. step-5-validation-and-compliance.md.
  13. step-6-integration-test-verification.md (live tests when feasible).

Prompt templates

  • prompt-template-single-agent-instructions.md
  • prompt-template-multi-agent-coordinator-instructions.md
  • prompt-template-multi-agent-subagent-instructions.md

These are instruction scaffolding, not runtime code.

CATAPA SDK notes

Public import:

from catapa import Catapa

Private/internal import:

from catapa_private import CatapaPrivate

SDK version: minimum >=0.3.3; TMO pins 0.3.6. Pin to latest compatible per DE.

Querying CATAPA API

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.

Guardrails

  • 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.

5. Integration patterns

When to use what

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

Export + notification pattern

For Excel/PDF artifacts + email:

  1. Generate artifact, upload to object storage, get presigned URL.
  2. Call google_mail sync method inside LangChain _run (async fails inside running event loop).
  3. Build email body in tool with _format_email_body() so markdown table/sections survive LLM collapse.
  4. Use real presigned URL; never leave placeholder variables.

Verifying sent email

Use gogcli (gog gmail get/search) rather than browser automation. Faster and authoritative.

6. Quick command cheat sheet

# 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.md

7. Digital Employee Core (digital-employee-core)

digital-employee-core is the Python framework library every CATAPA DE is built on. It wraps glaip-sdk and adds DE-specific primitives.

Install

uv add digital-employee-core
# or from CodeArtifact:
uv pip install --index-url https://aws:$CODEARTIFACT_AUTH_TOKEN@$CODEARTIFACT_REPOSITORY_ENDPOINT/simple/ digital-employee-core

Main exports

from 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,
)

What it provides

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

Relationship to GLAIP

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
Loading

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.

Current versions in repo

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.

Source

  • Local source: ~/Documents/github/CATAPA/CATAPA-SDK/main/python/digital-employee-core/
  • Published to AWS CodeArtifact catapa-digital-employee-core PyPI repo.
  • GitBook docs: same CATAPA Digital Employee section.

8. GLAIP CLI / TUI vs GLChat / Claudia

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
Loading
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

How Claudia differs

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:

  1. User submits message in Claudia UI.
  2. submitUserMessage server action builds a FormData payload.
  3. GLLMChatLanguageModel wraps Vercel AI SDK and posts to GL_API_URL/message.
  4. GLChat backend forwards to the selected agent / AIP runtime.
  5. SSE stream returns; UI renders it.

So: GLAIP = agent runtime; GLChat = chat platform; Claudia = CATAPA-branded client on top of GLChat.


9. Source-of-truth links

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.



Appendix: Full CATAPA Digital Employee GitBook

Source: https://gdplabs.gitbook.io/catapa

Catapa Developer Documentation Digital Employee

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Digital Employee


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Advanced Examples


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Build Verification Tests Bvt

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Build Verification Tests (BVT)

Overview

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.

When BVT Runs

BVT runs during deployment:

digital_employee.deploy()

By default, deploy() does this sequence:

  1. Build the resolved glaip_sdk.Agent instance
  2. Run self.verifier.verify(self.agent)
  3. Raise BuildVerificationError if any check failed
  4. 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.

Key Concepts

What the default verifier checks

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.

Pass, fail, and skip semantics

Each check produces a BVTCheckResult with one of three statuses:

  • passed: the check succeeded
  • failed: the check failed and should block deployment
  • skipped: the check was intentionally not run

Important behavior:

  • BVTResults.all_passed only returns False when at least one check is failed
  • skipped checks 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.

What causes deployment to fail

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

Default Verification Flow

The built-in DeploymentVerifier follows this sequence:

  1. Reset previous results
  2. Run _pre_checks(agent)
  3. Traverse the agent tree depth-first
  4. Run _check_agent_node(agent) for each visited node
  5. Verify each MCP on that node
  6. Run _post_checks(agent)

Each MCP verification does:

  1. Resolve the MCP config from agent.mcp_configs
  2. Extract the URL from config["config"]["url"]
  3. Validate the URL format
  4. Build an MCPConfiguration for create_session()
  5. 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.

Handling BVT Failures

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

Custom Verifiers

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.

Extension points

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.

Example: add pre-deployment policy checks

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

BVTStatus is a StrEnum with these values:

  • BVTStatus.PASSED
  • BVTStatus.FAILED
  • BVTStatus.SKIPPED

BVTCheckResult

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 checked
  • item_type: category such as mcp, agent, env_var, or policy
  • status: one of the BVTStatus values
  • message: human-readable result description
  • details: optional diagnostic metadata

BVTResults

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_passed
  • failed_checks
  • skipped_checks
  • summary()

Transport and Timeout Notes

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-stdio transports, the generated session config includes timeout=self.timeout
  • For stdio, the verifier applies asyncio.wait_for(..., timeout=self.timeout) around session.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 %}

Best Practices

1. Keep BVT enabled in normal deployments

The default deploy() behavior is correct for most cases. Bypass it only when you have a deliberate operational reason.

2. Treat skipped checks as signals

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.

3. Use custom verifiers for organization rules

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.

4. Use the built agent as the source of truth

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.

5. Log or surface results.summary()

When deployment fails, the BVT summary provides a concise diagnosis that is suitable for CI logs or deployment output.

Troubleshooting

BuildVerificationError during deploy

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

MCP check is skipped

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

URL format failure

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

Session initialization failure

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

API Summary

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

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Digital Employee Supervisor

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Digital Employee Supervisor

Overview

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.supervisor
  • DigitalEmployeeSupervisor(name=..., email=...)

Minimal Setup

1) Create a supervisor

{% code lineNumbers="true" %}

import os

from digital_employee_core import DigitalEmployeeSupervisor

supervisor = DigitalEmployeeSupervisor(
    name=os.getenv("SUPERVISOR_NAME", ""),
    email=os.getenv("SUPERVISOR_EMAIL", ""),
)

{% endcode %}

2) Attach the supervisor to the identity

{% 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 %}

Where It Is Used


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Escalation Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Escalation Configuration

Overview

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.

Key Concepts

Supervisor

Escalation is designed to reach a supervisor configured on the Digital Employee identity:

  • DigitalEmployeeIdentity.supervisor
  • DigitalEmployeeSupervisor(name=..., email=...)

If escalation is enabled and a supervisor exists, the escalation protocol is included in the generated prompt.

Escalation Channels

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.

Example

Step 1: Import dependencies

{% 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 %}

Step 2: Define the job with escalation instructions

{% 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 %}

Step 3: Configure the supervisor

{% 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_NAME
  • SUPERVISOR_EMAIL {% endhint %}

Step 4: Create the identity

{% code lineNumbers="true" %}

identity = DigitalEmployeeIdentity(
    name="Ops Assistant - Example",
    email="ops.assistant@example.com",
    job=job,
    supervisor=supervisor,
)

{% endcode %}

Step 5: Set up configurations

{% 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_URL
  • GOOGLE_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 %}

Step 6: Initialize the Digital Employee

{% code lineNumbers="true" %}

digital_employee = DigitalEmployee(
    identity=identity,
    mcps=[google_docs_mcp],
    configurations=configurations,
)

{% endcode %}

Step 7: Enable escalation and add channels

{% code lineNumbers="true" %}

digital_employee.enable_escalation()
digital_employee.add_escalation_channel(GoogleMailMCPEscalationChannel())

{% endcode %}

Step 8: Deploy and run

{% code lineNumbers="true" %}

digital_employee.deploy()
result = digital_employee.run(message="Read google docs with document_id='123413'")

{% endcode %}

Deployment Order Matters

  • Enable escalation and add channels before calling digital_employee.deploy().
  • The deploy step will bundle any required MCP connectors/tools introduced by escalation channels.

Custom 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(...).

1) Implement the 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.

2) Register the channel before deploy

digital_employee.enable_escalation()
digital_employee.add_escalation_channel(CustomEscalationChannel())
digital_employee.deploy()

Notes

  • One instance per channel type: EscalationChannel equality 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).

Best Practices

  • 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.

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Extend

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Extend

Some digital employee core components can be extended, for example, identity, connectors (tools & MCPs), and the digital employee object itself.

Extend Use Cases

Identity

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 %}

Connectors

Tools

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,6

MCPs

Here 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}

Digital Employee

{% 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 %}


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Instantiation

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Instantiation

Prerequisites

For these examples, you will need to:

Instantiate Digital Employee

Import the Package

{% 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 %}

Initialize the Extended Digital Employee

{% 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 %}

Run the Extended Digital Employee

{% 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:

  1. Replace [gl-connectors-x-api-key] with x-api-key from GL Connectors. See below for one way to do it.
  2. (Optional) Replace NEW_GOOGLE_CALENDAR_MCP_URL if you are using a different GL Connectors server instance.
🔑 Get GL Connectors x-api-key
  1. Open https://api.bosa.id/console, then sign in.
  2. 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] .
  1. If your Gmail account has not been integrated yet, continue with the steps below.
  2. Under Available Modules section, find the Google_mail integration and click Add New Integration button.
  1. An authorization URL will appear. Click or copy the URL, then authenticate using your Gmail account.
  1. Below is an example of a successfully integrated Gmail account.
{% endhint %}

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Mcp Allowed Tools Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

MCP Allowed Tools Configuration

Overview

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.

Key Concepts

What are Allowed Tools?

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.

Benefits

  • 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

Configuration

Basic Setup

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 %}

Key Pattern

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).

Value Format

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 %}

Complete Example

Step 1: Import Required Components

{% 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 %}

Step 2: Create Identity

{% 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 %}

Step 3: Configure MCP URLs and Allowed Tools

{% 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 endpoints
  • GOOGLE_MCP_X_API_KEY: Your actual API key (consider using environment variables) {% endhint %}

Step 4: Create and Deploy the Digital Employee

{% 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 %}

Step 5 (Optional): Verify Configuration

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 %}

Step 6: Run the Digital Employee

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_emails to find the latest email.
  • It cannot use tools like google_mail_delete_email because 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.

Common Use Cases

Restricting Email Operations

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 %}

Read-Only Calendar Access

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 %}

Multiple Tool Permissions

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 %}

Best Practices

1. Principle of Least Privilege

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 %}

2. Document Your Tool Choices

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 %}

3. Use Environment Variables for Sensitive Data

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 %}

Troubleshooting

Tools Not Being Restricted

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

Tool Names Incorrect

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.


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Memory Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Memory Configuration

Overview

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_id on every run() / arun() call.

Key Concepts

What is memory_user_id?

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 Provider

Memory is enabled by setting AgentConfigKeys.MEMORY to a provider (e.g. MemoryProvider.MEM0). Currently we utilize GL SDK Memory via AIP Memory.

Minimal Example

1) Create a memory-enabled Digital Employee

{% 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 %}

2) Deploy, then call with a stable memory_user_id

{% 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 %}

3) Different user, different memory

{% 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 %}

Local Usage

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 %}

Notes / Best Practices

  • memory_user_id is 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).

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Programmatic Tool Calling Ptc Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Programmatic Tool Calling (PTC) Configuration

Overview

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.

Key Concepts

Why use PTC?

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

How PTC works

  1. The agent receives a task requiring multiple tool calls.
  2. The agent writes a Python script and invokes execute_ptc_code.
  3. The script runs inside an E2B sandbox with all registered tools available.
  4. Only the final printed output is returned to the agent's context.

The PTC configuration object

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.

Prerequisites

  • E2B_API_KEY must be set (get one at https://e2b.dev)
  • OPENAI_API_KEY (or another supported model key) must be set
  • glaip-sdk installed with [local] extras

Complete Example

The file examples/ptc/ptc_example.py demonstrates the PTC workflow.

Step 1: Import required components

{% 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 %}

Step 2: Create the Digital Employee identity

{% 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 %}

Step 3: Configure PTC and attach tools

{% 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.

Step 4: Run locally

{% 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. Use run(..., local=True) directly.

What this example shows

  • PTC is activated with PTC(enabled=True).
  • Tools are registered on the Digital Employee — no separate sandbox configuration needed.
  • The agent can orchestrate time_tool and calculator_tool in 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.

Configuration Reference

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 the DigitalEmployee.tools list.

Common Use Cases

Parallel tool calls

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 %}

Installing extra sandbox packages

{% code lineNumbers="true" %}

ptc_config = PTC(
    enabled=True,
    ptc_packages=["pandas==2.2.0", "numpy"],
)

{% endcode %}

Extending the sandbox timeout for long-running tasks

{% code lineNumbers="true" %}

ptc_config = PTC(
    enabled=True,
    sandbox_timeout=300.0,   # 5 minutes
    default_tool_timeout=90.0,
)

{% endcode %}

Best Practices

1. Mention execute_ptc_code in the job instruction

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.

2. Keep sandbox_timeout proportional to task complexity

A short timeout is fine for quick calculations; increase it for tasks that involve many sequential tool calls or heavy data processing.

3. Use ptc_packages only when needed

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.

4. Do not call deploy() for local PTC runs

PTC is a local-only feature. Call run(..., local=True) directly — skip deploy().

5. Use a single DigitalEmployee instance per session

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.

Troubleshooting

E2B_API_KEY not set

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 %}

glaip-sdk[local] not installed

Problem: Import errors or missing sandbox dependencies.

Solution: Install the local extras:

{% code lineNumbers="true" %}

poetry add "glaip-sdk[local]"

{% endcode %}

Agent not using execute_ptc_code

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).

Sandbox timeout exceeded

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 %}

Tool not available inside the sandbox

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.


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Recommended Project Structure

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Recommended Project Structure

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

Agents

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

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.

Connectors

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

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.


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Run History

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Run History

Overview

We use GLAIP Audit Trails to review a Digital Employee agent’s run history for auditing.

Retrieve Run History (via AIP CLI)

1) Start the AIP CLI

Run:

aip

2) Open the agents list

In the CLI:

/agents

Find the agent by Digital Employee name, then select the matching agent entry.

4) View runs

In the CLI:

/runs

5) Review runs in the TUI

The console will display a TUI list of runs (run history) for the selected agent.


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Scheduler Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Scheduler Configuration

Overview

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 %}

Key Concepts

What is a Scheduler?

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

Benefits

  • 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

Configuration

Basic Components

Schedules are configured using two main classes:

  1. ScheduleConfig: Defines the timing using cron-like parameters
  2. ScheduleItemConfig: Combines the schedule with the input to execute

Schedule Configuration Parameters

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.

Cron Syntax Guide

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).

Complete Example

Step 1: Import Required Components

{% 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 %}

Step 2: Create Digital Employee Identity

{% 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 %}

Step 3: Configure Schedule

{% 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 %}

Step 4: Create Schedule Item

{% code lineNumbers="true" %}

# Create schedule item with input
morning_schedule_item = ScheduleItemConfig(
    schedule_config=morning_schedule,
    input="Morning greeting"
)

{% endcode %}

Step 5: Create and Deploy Digital Employee

{% 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 %}

Step 6: Verify Schedules (Optional)

{% 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 %}

Step 7: Monitor Schedule Runs (Optional)

{% 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 %}

Common Use Cases

Daily Morning Report

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 %}

Hourly Data Check

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 %}

Weekly Summary

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 %}

Monthly Reminder

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 %}

Every 15 Minutes

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 %}

Best Practices

1. Use Descriptive Inputs

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 %}

2. Consider Time Zones

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 %}

3. Avoid Overlapping Schedules

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 %}

4. Test Schedule Timing

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 %}

5. Monitor Schedule Execution

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 %}

6. Use Multiple Schedules Strategically

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 %}

Advanced Configuration

Dynamic Schedule Inputs

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 %}

Combining with Other Features

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 %}

Troubleshooting

Schedule Not Triggering

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)

Incorrect Timing

Problem: Schedule runs at unexpected times.

Solution:

  • Verify time zone settings
  • Double-check cron parameters (especially day_of_week where 0=Monday)
  • Test with to_cron_string() to see the actual cron expression
  • Review the next_run_time field in the schedule object

Schedule Runs Failed

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

Multiple Schedules Conflict

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

API Reference

ScheduleConfig

{% 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 %}

ScheduleItemConfig

{% 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 %}

DigitalEmployee with Schedules

{% 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 %}

Managing Schedules via Agent

{% 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 %}

Examples in Repository

For complete working examples, see:


Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Skills Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Skills Configuration

Overview

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.

Key Concepts

What is a skill?

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(...)

How skills are attached

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.

Skill source types

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

Remote GitHub Skills Example

The file examples/skills_example.py demonstrates the remote workflow.

1) Create the Digital Employee identity

{% 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 %}

2) Attach a GitHub-hosted skill

{% 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 %}

3) Deploy before running

{% 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 %}

What this example shows

  • The Digital Employee accepts a GitHub URL as a skill source
  • Remote skills are used in the normal deployed workflow
  • deploy() is called before run(...)

Private GitHub repositories

The example notes that public repositories work directly. For private repositories, you must provide one of these environment variables:

  • GITHUB_PERSONAL_ACCESS_TOKEN
  • GITHUB_TOKEN
  • GH_TOKEN

Local Skills Example

The directory examples/local_skills/ demonstrates the local workflow.

1) Define the skill directory

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 %}

2) Load the local skill

{% code lineNumbers="true" %}

from pathlib import Path

skill_path = Path(__file__).parent / ".agents/skills/haiku-standup"

{% endcode %}

3) Load the local skill

{% code lineNumbers="true" %}

from glaip_sdk.skills import Skill

local_skill = Skill.from_path(str(skill_path))

{% endcode %}

4) Attach the skill to a Digital Employee

{% 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 %}

5) Run locally without deployment

{% 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 %}

What this example shows

  • 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()

When to Use Which Approach

Use remote GitHub skills when

  • 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

Use local skills when

  • 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

Best Practices

1. Keep skills narrowly scoped

Write each skill for a specific job, such as copywriting or standup formatting, rather than combining many unrelated behaviors into one skill.

2. Use deploy() only for remote workflows

Follow the examples:

  • Remote GitHub skill example: call deploy() before run(...)
  • Local path-based skill example: skip deploy and call run(..., local=True)

3. Validate the local path before running

The local example checks that the path exists before creating the employee. This is a good pattern when developing local skills.

4. Keep SKILL.md explicit

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

Troubleshooting

Local skill is not being found

Problem: The local skill does not load.

Solution:

  • Verify the directory path is correct
  • Verify SKILL.md exists at the root of the skill directory
  • Verify you are calling Skill.from_path(str(skill_path))

Local skill does not behave as expected

Problem: The output ignores the intended format.

Solution:

  • Make the instructions in SKILL.md more explicit
  • Add stricter output templates and rules
  • Ensure the employee instruction does not conflict with the skill behavior

Remote GitHub skill cannot be accessed

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, or GH_TOKEN
  • Ensure you call deploy() before run(...)

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples Sub Agents Configuration

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Sub Agents Configuration

Overview

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).

Key Concepts

What is a “sub-agent”?

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.

How sub-agents are attached

You attach sub-agents by providing them to DigitalEmployee:

  • DigitalEmployee(sub_agents=[...]) (recommended)
  • digital_employee.add_sub_agents([...])

Configuration propagation (tools + MCPs)

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.Agent is supported as a sub-agent type; unsupported types are skipped with a warning.

Minimal Example

1) Create a coordinator Digital Employee

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)

2) Define one or more sub-agents

from glaip_sdk import Agent

reminder_agent = Agent(
    name="ReminderAgent",
    instruction="You are a reminder specialist.",
)

3) Attach sub-agents and deploy

from digital_employee_core import DigitalEmployee

digital_employee = DigitalEmployee(
    identity=identity,
    sub_agents=[reminder_agent],
)

digital_employee.deploy()

Notes / Best Practices

  • Add or remove sub-agents before deploy; changes after deploy require re-deploying to update the agent graph.
  • Prefer stable, unique Agent.name values (removal uses name matching).
  • If sub-agents use MCPs/tools that require configs, prefer putting shared configs on the parent DigitalEmployee.configurations and rely on propagation; override only when a sub-agent needs different settings.

Agent Instructions

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.

Querying This Documentation

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.

Advanced Examples User Information In Digital Employee Runs

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

User Information in Digital Employee Runs

Overview

Digital Employee Core handles user information in two different ways:

  • User-scoped memory uses memory_user_id to 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:

For GL Connectors integration setup, see Integration Setup.

Key Concepts

memory_user_id

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

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

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.

GL AIP handoff

Tools should not parse the original GL IAM delegation token object directly. Treat GL AIP as the boundary that:

  1. Receives the delegation token object described in Delegate to Agent.
  2. Validates the delegation token as described in Validate Delegation Token.
  3. 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.

Runtime User Context in RunnableConfig

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.

User-Scoped Memory Example

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_id is required. Digital Employee Core raises an error when run() or arun() is called without it.

User Information in Custom Tools

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_schema on the tool.
  • Read static config with self.get_tool_config(config).
  • Read delegated user tokens from RunnableConfig.metadata, such as config.get("metadata").get("gl_connectors_token").

1) Create a custom tool

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 %}

2) Add user_authentication in tool config

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: true

The config key must match the tool name:

class UserProfileTool(BaseTool):
    name: str = "user_profile_tool"

3) Handle delegated tokens in tool logic

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_key authenticates the tool to GL Connectors.
  • gl_connectors_token identifies the current delegated user in GL Connectors.
  • auth_string contains the downstream integration credential for that delegated user.
  • user_authentication: true signals that GL AIP should delegate the appropriate token to the tool.

For GL Connectors setup details, see GL Connectors Integration Setup.

Configuration Propagation to Sub-Agents

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.

Integrate the Custom Tool in Digital Employee

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 %}

Notes / Best Practices

  • 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 from RunnableConfig.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_id only for memory scoping. Do not use it as an authorization token.
  • Enable user_authentication only 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.

Troubleshooting

Memory is enabled but the run fails

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",
)

Tool does not receive the user token

Check that:

  1. The tool config includes user_authentication: true.
  2. The run call passes the integration token, for example gl_connectors_token.
  3. The token is available in the environment when running locally.
export GL_CONNECTORS_TOKEN="<delegated_token>"

Agent Instructions

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.

Querying This Documentation

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.

Digital Employee Architecture

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Digital Employee Architecture

Overview

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 %}

Core Components

The architecture consists of three primary layers: the User Interaction Layer, the Digital Employee instance, and the underlying AI Agent Platform (AIP).

1. User Interaction Layer

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.

2. Digital Employee

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.

3. AI Agent Platform (AIP)

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.

Integration Architecture: Model Context Protocol (MCP)

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.

Google Workspace Integration

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.

External Platform Integration

In addition to Google Workspace, the system connects to specialized enterprise platforms.

SQL Tool MCP

  • 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.

Evalground MCP

  • 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.

Tools Framework

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.

Built-in 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.

User-Defined Tools

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.

Key Benefits

{% 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 %}


Agent Instructions

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.

Querying This Documentation

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.

Digital Employee Architecture Digital Employee Detailed Block Diagram

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Digital Employee Detailed Block Diagram

Digital Employee Overview

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).

Why a Pipeline Is Introduced

  1. Efficiency: Fewer LLM calls — reduced operational cost
  2. Maintainability: Modular step design — independent changes without cascading effects
  3. Reproducibility: Deterministic execution — same inputs produce same outputs
  4. Responsiveness: Fewer LLM calls — reduced end-to-end latency
  5. Testability: Isolated step boundaries — independent testing of each step

Digital Employee Application

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.


Agent Instructions

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.

Querying This Documentation

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.

Digital Employee Architecture Tech Stack Overview

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Tech Stack Overview

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.


Agent Instructions

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.

Querying This Documentation

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.

Getting Started Example

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Getting Started Example

This quickstart guide walks you through a simple setup to create a digital employee in just a few minutes.

Prerequisites

To follow this example, you will need to:

  • Install the digital-employee-core package
  • If you are planning to deploy and/or the digital employee in a remote AIP server, set the AIP_API_URL and AIP_API_KEY environment variables in your terminal or in a .env file

{% 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_KEY environment variables in your terminal or in a .env file

{% 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 %}

Build Digital Employee - A Basic Example

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.

Import the Package

{% code lineNumbers="true" %}

from digital_employee_core import (
    DigitalEmployee,
    DigitalEmployeeConfiguration,
    DigitalEmployeeIdentity,
    DigitalEmployeeJob,
)
from digital_employee_core.connectors.mcps import google_mail_mcp

{% endcode %}

Initialize the Digital Employee

{% 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 %}

Run the Digital Employee Locally

{% 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 %}

Run the Digital Employee in Remote AIP Server

{% 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:

  1. Replace [your-email] with your email address.
  2. Replace [gl-connectors-x-api-key] with x-api-key from GL Connectors. See below for one way to do it.
  3. (Optional) Replace GOOGLE_MAIL_MCP_URL if you are using a different GL Connectors server instance.
🔑 Get GL Connectors x-api-key
  1. Open https://api.bosa.id/console, then sign in.
  2. 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] .
  1. If your Gmail account has not been integrated yet, continue with the steps below.
  2. Under Available Modules section, find the Google_mail integration and click Add New Integration button.
  1. An authorization URL will appear. Click or copy the URL, then authenticate using your Gmail account.
  1. Below is an example of a successfully integrated Gmail account.
{% endhint %}

Agent Instructions

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.

Querying This Documentation

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.

Install And Configure

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Install and Configure

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 %}


Agent Instructions

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.

Querying This Documentation

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.

Multi Tenant

For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to page URLs; this page is available as Markdown.

Multi-tenant

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

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

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

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.


Agent Instructions

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.

Querying This Documentation

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment