← All code samples

Integration secrets → agent → Ask

Provision an integration with its secrets in one call, wire an agent to it, then optionally start Ask. Pass a plain key/value map; the SDK resolves Vault’s required metadata keys, stores the secret, binds secret_ref_ids, and upserts the integration.

  1. upsert_integration_with_secrets / upsertIntegrationWithSecrets
  2. get_integration(..., include_tools=True) / getIntegration({ includeTools: true })
  3. register_agent / update_agent with integrations: [name] (full body: HITL, world model, runners)
  4. Optionally start_ask / startAsk with mode="simple" or "plan"

When you need this

  • Stand up a tool-backed agent from CI. Pass vendor credentials once as a normal map (token, or Grafana base_url + api_token, or Jira base_url + email + api_token). The SDK matches Vault metadata so you do not hand-build [{key, value}] arrays.
  • Override discovery when needed. Pass vault_category / vault_subcategory (TS: vaultCategory / vaultSubcategory) if the catalog lookup is wrong for a custom type.
  • Keep a granular escape hatch. Advanced code can still vault.store_github_secret then upsert_integration(..., secret_ref_ids=[…]), or call vault.get_supported_metadata / getSupportedMetadata first.
  • Pin Ask execution. Use mode="plan" for ReAcTree digs or "simple" for a root-only turn.

How to run

export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>

# Prefer a real vendor token when you have one:
export GH_TOKEN=ghs_…                    # or GITHUB_TOKEN
# or:
# export GRAFANA_BASE_URL=https://…
# export GRAFANA_API_TOKEN=…

pip install stackgen-sdk==0.4.0
python examples/automation/agent-vault-ask.py
python examples/automation/agent-vault-ask.py --ask --mode simple

Without GH_TOKEN / Grafana envs the sample still calls the combined helper with a synthetic GitHub token and skip_connectivity_check so CI can exercise the shape. Set a real token for a live vendor binding.

Happy path

from stackgen import StackgenClient, StackgenConfig

client = StackgenClient(
    StackgenConfig(
        base_url="https://app.stackgen.com",
        api_token="stackgen_…",
        workspace_id="<workspace-uuid>",
    )
)

result = client.aiden.upsert_integration_with_secrets(
    "ops-github",
    type="github",
    secrets={"token": "ghs_…"},
    description="GitHub for ops bot",
)
print(result["secret_ids"], result["integration"]["name"])

client.aiden.register_agent({
    "name": "ops-bot",
    "persona": "Help with GitHub tasks.",
    "integrations": ["ops-github"],
    "hitl": {"always_allowed": ["*"]},
})

start = client.aiden.start_ask(
    "Summarize open PRs",
    entity_refs=[{"name": "ops-bot", "type": "agent"}],
    mode="simple",
)
print(start["trace_id"], start["session_id"])

Granular path (advanced)

When you need to reuse an existing Vault secret or split store from bind:

secret = client.vault.store_github_secret(name="gh-ops", token="ghs_…")
client.aiden.upsert_integration(
    "ops-github",
    {
        "type": "github",
        "description": "GitHub for ops bot",
        "scope": "TENANT",
        "enabled": True,
        "secret_ref_ids": [secret["id"]],
    },
)
detail = client.aiden.get_integration("ops-github", include_tools=True)

Remote runners use typed_secret_refs (keyed by subcategory). Integrations use secret_ref_ids (ordered UUID list).

Runnable CLI

#!/usr/bin/env python3
"""Provision a GitHub/Grafana integration with secrets, attach an agent, Ask.

Happy path uses ``upsert_integration_with_secrets`` so the SDK resolves Vault
metadata keys from a plain secrets map, stores the secret, binds
``secret_ref_ids``, and upserts the integration in one call. Then
register/update an agent that uses that integration and optionally start Ask
with ``mode``.

Env:
  STACKGEN_URL, STACKGEN_TOKEN, STACKGEN_WORKSPACE_ID (required)
  GH_TOKEN or GITHUB_TOKEN — prefer GitHub integration
  GRAFANA_BASE_URL + GRAFANA_API_TOKEN — else Grafana
  Without vendor tokens, uses a synthetic GitHub token + skip connectivity
  (fine for CI shape checks; not a live vendor connection).

Usage:
  python examples/automation/agent-vault-ask.py
  python examples/automation/agent-vault-ask.py --ask --mode simple
"""

from __future__ import annotations

import argparse
import os
import sys
import uuid

from stackgen import StackgenClient, StackgenConfig


def env(name: str, *fallbacks: str) -> str:
    for key in (name, *fallbacks):
        value = (os.environ.get(key) or "").strip()
        if value:
            return value
    raise SystemExit(f"missing env {name}")


def optional_env(*names: str) -> str:
    for name in names:
        value = (os.environ.get(name) or "").strip()
        if value:
            return value
    return ""


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--ask",
        action="store_true",
        help="Also start Ask against the agent (mode defaults to simple)",
    )
    parser.add_argument(
        "--mode",
        default="simple",
        choices=("simple", "plan"),
        help="Ask mode pin when --ask is set",
    )
    args = parser.parse_args()

    client = StackgenClient(
        StackgenConfig(
            base_url=env("STACKGEN_URL"),
            api_token=env("STACKGEN_TOKEN"),
            workspace_id=env("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT"),
        )
    )

    suffix = uuid.uuid4().hex[:8]
    integration_name = f"sdk-int-{suffix}"
    agent_name = f"sdk-agent-{suffix}"
    secret_ids: list[str] = []

    gh_token = optional_env("GH_TOKEN", "GITHUB_TOKEN")
    grafana_url = optional_env("GRAFANA_BASE_URL", "STACKGEN_GRAFANA_BASE_URL")
    grafana_token = optional_env("GRAFANA_API_TOKEN", "STACKGEN_GRAFANA_API_TOKEN")

    try:
        # --- Happy path: Vault + bind + upsert in one helper ---
        if gh_token:
            provisioned = client.aiden.upsert_integration_with_secrets(
                integration_name,
                type="github",
                secrets={"token": gh_token},
                description="sdk sample GitHub integration",
                skip_connectivity_check=True,
            )
            print(f"upsert_integration_with_secrets type=github name={integration_name}")
        elif grafana_url and grafana_token:
            provisioned = client.aiden.upsert_integration_with_secrets(
                integration_name,
                type="grafana",
                secrets={"base_url": grafana_url, "api_token": grafana_token},
                description="sdk sample Grafana integration",
                skip_connectivity_check=True,
            )
            print(f"upsert_integration_with_secrets type=grafana name={integration_name}")
        else:
            # CI / local without vendor tokens: still exercise the combined helper.
            provisioned = client.aiden.upsert_integration_with_secrets(
                integration_name,
                type="github",
                secrets={"token": f"sdk-sample-not-a-real-token-{suffix}"},
                description="sdk sample GitHub binding (synthetic token)",
                skip_connectivity_check=True,
            )
            print(
                f"upsert_integration_with_secrets type=github name={integration_name} "
                "(synthetic token; set GH_TOKEN for a real binding)"
            )

        secret_ids = list(provisioned.get("secret_ids") or [])
        print(f"secret_ids={secret_ids}")

        # Advanced (granular) alternative — store then bind yourself:
        #   secret = client.vault.store_github_secret(name=..., token=...)
        #   client.aiden.upsert_integration(
        #       integration_name,
        #       {
        #           "type": "github",
        #           "description": "...",
        #           "scope": "TENANT",
        #           "enabled": True,
        #           "secret_ref_ids": [secret["id"]],
        #       },
        #   )

        detail = client.aiden.get_integration(integration_name, include_tools=True)
        tools = detail.get("discovered_tools") or detail.get("tools") or []
        print(
            f"get_integration name={integration_name} "
            f"tools={len(tools) if isinstance(tools, list) else 'n/a'}"
        )

        client.aiden.register_agent(
            {
                "name": agent_name,
                "persona": "Ops assistant. Prefer integration tool evidence.",
                "description": "sdk sample agent wired to a Vault-backed integration",
                "integrations": [integration_name],
                "world_model_enabled": False,
                "hitl": {"always_allowed": ["*"]},
            }
        )
        print(f"register_agent name={agent_name} integrations=[{integration_name}]")

        client.aiden.update_agent(
            agent_name,
            {
                "description": "sdk sample agent (updated)",
                "integrations": [integration_name],
            },
        )
        print("update_agent ok")

        if args.ask:
            start = client.aiden.start_ask(
                "SDK sample: reply with one short sentence about your tools.",
                entity_refs=[{"name": agent_name, "type": "agent"}],
                mode=args.mode,
            )
            print(
                f"start_ask mode={args.mode} trace_id={start.get('trace_id')} "
                f"session_id={start.get('session_id')}"
            )
    finally:
        try:
            client.aiden.deregister_agent(agent_name)
            print(f"deregister_agent name={agent_name}")
        except Exception as err:  # noqa: BLE001
            print(f"deregister_agent failed: {err}", file=sys.stderr)
        try:
            client.aiden.delete_integration(integration_name)
            print(f"delete_integration name={integration_name}")
        except Exception as err:  # noqa: BLE001
            print(f"delete_integration failed: {err}", file=sys.stderr)
        for secret_id in secret_ids:
            try:
                client.vault.delete_secret(secret_id)
                print(f"delete_secret id={secret_id}")
            except Exception as err:  # noqa: BLE001
                print(f"delete_secret failed: {err}", file=sys.stderr)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())