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.
upsert_integration_with_secrets/upsertIntegrationWithSecretsget_integration(..., include_tools=True)/getIntegration({ includeTools: true })register_agent/update_agentwithintegrations: [name](full body: HITL, world model, runners)- Optionally
start_ask/startAskwithmode="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 Grafanabase_url+api_token, or Jirabase_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_secretthenupsert_integration(..., secret_ref_ids=[…]), or callvault.get_supported_metadata/getSupportedMetadatafirst. - 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 simpleexport 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=…
npm install stackgen-sdk@0.4.0 tsx
npx tsx examples/automation/agent-vault-ask.ts
npx tsx examples/automation/agent-vault-ask.ts --ask --mode simpleexport STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_WORKSPACE_ID=<workspace-uuid>
export GH_TOKEN=ghs_…
go run ./examples/go/automation/agent-vault-ask.go
go run ./examples/go/automation/agent-vault-ask.go --ask --mode simpleWithout 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"])import { StackgenClient, StackgenConfig } from "stackgen-sdk";
const client = new StackgenClient(
new StackgenConfig({
baseUrl: "https://app.stackgen.com",
apiToken: "stackgen_…",
workspaceId: "<workspace-uuid>",
}),
);
const result = await client.aiden.upsertIntegrationWithSecrets({
name: "ops-github",
type: "github",
secrets: { token: "ghs_…" },
description: "GitHub for ops bot",
});
console.log(result.secretIds, result.integration.name);
await client.aiden.registerAgent({
name: "ops-bot",
persona: "Help with GitHub tasks.",
integrations: ["ops-github"],
hitl: { always_allowed: ["*"] },
});
const start = await client.aiden.startAsk("Summarize open PRs", {
entityRefs: [{ name: "ops-bot", type: "agent" }],
mode: "simple",
});
console.log(start.trace_id, start.session_id);result, err := client.UpsertIntegrationWithSecretsContext(ctx, stackgen.UpsertIntegrationWithSecretsOptions{
Name: "ops-github",
Type: "github",
Secrets: map[string]string{"token": "ghs_…"},
Description: "GitHub for ops bot",
})
if err != nil { return err }
fmt.Println(result.SecretIDs, result.Integration["name"])
if _, err = client.Aiden.RegisterAgent(ctx, map[string]any{
"name": "ops-bot",
"persona": "Help with GitHub tasks.",
"integrations": []string{"ops-github"},
"hitl": map[string]any{"always_allowed": []string{"*"}},
}); err != nil { return err }
start, err := client.Aiden.StartAsk(ctx, "Summarize open PRs", aiden.StartAskOptions{
EntityRefs: []aiden.EntityRef{{Name: "ops-bot", Type: "agent"}},
Mode: "simple",
})
if err != nil { return err }
fmt.Println(start.TraceID, start.SessionID)
return nilGranular 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)const secret = await client.vault.storeGithubSecret({
name: "gh-ops",
token: "ghs_…",
});
await client.aiden.upsertIntegration("ops-github", {
type: "github",
description: "GitHub for ops bot",
scope: "TENANT",
enabled: true,
secret_ref_ids: [String(secret.id)],
});
const detail = await client.aiden.getIntegration("ops-github", {
includeTools: true,
});secret, err := client.Vault.StoreGithubSecret("gh-ops", "ghs_…", "", nil, false)
if err != nil { return err }
if _, err = client.Aiden.UpsertIntegration(ctx, "ops-github", map[string]any{
"type": "github",
"description": "GitHub for ops bot",
"scope": "TENANT",
"enabled": true,
"secret_ref_ids": []any{secret["id"]},
}); err != nil { return err }
_, err = client.Aiden.GetIntegration(ctx, "ops-github")
return errRemote runners use typed_secret_refs (keyed by subcategory). Integrations use
secret_ref_ids (ordered UUID list).
Runnable CLI
- Python:
examples/automation/agent-vault-ask.py - TypeScript:
examples/automation/agent-vault-ask.ts - Go:
examples/go/automation/agent-vault-ask.go
#!/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())#!/usr/bin/env npx tsx
/**
* Provision a GitHub/Grafana integration with secrets, attach an agent, Ask.
*
* Happy path uses `upsertIntegrationWithSecrets` 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.
*
* Env:
* STACKGEN_URL, STACKGEN_TOKEN, STACKGEN_WORKSPACE_ID (required)
* GH_TOKEN / GITHUB_TOKEN — prefer GitHub
* GRAFANA_BASE_URL + GRAFANA_API_TOKEN — else Grafana
* Without vendor tokens, uses a synthetic GitHub token + skip connectivity.
*
* Usage:
* npx tsx examples/automation/agent-vault-ask.ts
* npx tsx examples/automation/agent-vault-ask.ts --ask --mode simple
*/
import { randomUUID } from "node:crypto";
import { StackgenClient, StackgenConfig } from "stackgen-sdk";
function env(name: string, ...fallbacks: string[]): string {
for (const key of [name, ...fallbacks]) {
const value = (process.env[key] || "").trim();
if (value) {
return value;
}
}
throw new Error(`missing env ${name}`);
}
function optionalEnv(...names: string[]): string {
for (const name of names) {
const value = (process.env[name] || "").trim();
if (value) return value;
}
return "";
}
function argValue(flag: string): string {
const idx = process.argv.indexOf(flag);
if (idx < 0) return "";
return (process.argv[idx + 1] || "").trim();
}
async function main(): Promise<void> {
const doAsk = process.argv.includes("--ask");
const mode = (argValue("--mode") || "simple") as "simple" | "plan";
if (mode !== "simple" && mode !== "plan") {
throw new Error("--mode must be simple or plan");
}
const client = new StackgenClient(
new StackgenConfig({
baseUrl: env("STACKGEN_URL"),
apiToken: env("STACKGEN_TOKEN"),
workspaceId: env("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT"),
}),
);
const suffix = randomUUID().slice(0, 8);
const integrationName = `sdk-int-${suffix}`;
const agentName = `sdk-agent-${suffix}`;
let secretIds: string[] = [];
const ghToken = optionalEnv("GH_TOKEN", "GITHUB_TOKEN");
const grafanaUrl = optionalEnv("GRAFANA_BASE_URL", "STACKGEN_GRAFANA_BASE_URL");
const grafanaToken = optionalEnv("GRAFANA_API_TOKEN", "STACKGEN_GRAFANA_API_TOKEN");
try {
// Happy path: Vault + bind + upsert in one helper
let provisioned;
if (ghToken) {
provisioned = await client.aiden.upsertIntegrationWithSecrets({
name: integrationName,
type: "github",
secrets: { token: ghToken },
description: "sdk sample GitHub integration",
skipConnectivityCheck: true,
});
console.log(`upsertIntegrationWithSecrets type=github name=${integrationName}`);
} else if (grafanaUrl && grafanaToken) {
provisioned = await client.aiden.upsertIntegrationWithSecrets({
name: integrationName,
type: "grafana",
secrets: { base_url: grafanaUrl, api_token: grafanaToken },
description: "sdk sample Grafana integration",
skipConnectivityCheck: true,
});
console.log(`upsertIntegrationWithSecrets type=grafana name=${integrationName}`);
} else {
provisioned = await client.aiden.upsertIntegrationWithSecrets({
name: integrationName,
type: "github",
secrets: { token: `sdk-sample-not-a-real-token-${suffix}` },
description: "sdk sample GitHub binding (synthetic token)",
skipConnectivityCheck: true,
});
console.log(
`upsertIntegrationWithSecrets type=github name=${integrationName} ` +
"(synthetic token; set GH_TOKEN for a real binding)",
);
}
secretIds = [...(provisioned.secretIds || [])];
console.log(`secretIds=${JSON.stringify(secretIds)}`);
// Advanced (granular) alternative — store then bind yourself:
// const secret = await client.vault.storeGithubSecret({ name, token });
// await client.aiden.upsertIntegration(integrationName, {
// type: "github",
// description: "...",
// scope: "TENANT",
// enabled: true,
// secret_ref_ids: [String(secret.id)],
// });
const detail = await client.aiden.getIntegration(integrationName, {
includeTools: true,
});
const tools = (detail.discovered_tools ?? detail.tools ?? []) as unknown[];
console.log(
`getIntegration name=${integrationName} tools=${Array.isArray(tools) ? tools.length : "n/a"}`,
);
await client.aiden.registerAgent({
name: agentName,
persona: "Ops assistant. Prefer integration tool evidence.",
description: "sdk sample agent wired to a Vault-backed integration",
integrations: [integrationName],
world_model_enabled: false,
hitl: { always_allowed: ["*"] },
});
console.log(`registerAgent name=${agentName} integrations=[${integrationName}]`);
await client.aiden.updateAgent(agentName, {
description: "sdk sample agent (updated)",
integrations: [integrationName],
});
console.log("updateAgent ok");
if (doAsk) {
const start = await client.aiden.startAsk(
"SDK sample: reply with one short sentence about your tools.",
{
entityRefs: [{ name: agentName, type: "agent" }],
mode,
},
);
console.log(
`startAsk mode=${mode} trace_id=${start.trace_id} session_id=${start.session_id}`,
);
}
} finally {
try {
await client.aiden.deregisterAgent(agentName);
console.log(`deregisterAgent name=${agentName}`);
} catch (err) {
console.error(`deregisterAgent failed: ${err}`);
}
try {
await client.aiden.deleteIntegration(integrationName);
console.log(`deleteIntegration name=${integrationName}`);
} catch (err) {
console.error(`deleteIntegration failed: ${err}`);
}
for (const secretId of secretIds) {
try {
await client.vault.deleteSecret(secretId);
console.log(`deleteSecret id=${secretId}`);
} catch (err) {
console.error(`deleteSecret failed: ${err}`);
}
}
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});// Store an integration secret, register an agent, and optionally start Ask.
//
// Documented at https://appcd-dev.github.io/stackgen-sdk/code-samples/agent-vault-ask/
//
// export GH_TOKEN=ghs_…
// go run ./examples/go/automation/agent-vault-ask.go --ask
package main
import (
"context"
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"os"
"strings"
stackgen "github.com/appcd-dev/stackgen-sdk/clients/go"
"github.com/appcd-dev/stackgen-sdk/clients/go/aiden"
)
func envOr(keys ...string) string {
for _, key := range keys {
if v := strings.TrimSpace(os.Getenv(key)); v != "" {
return v
}
}
return ""
}
func main() {
ask := flag.Bool("ask", false, "Also start Ask against the agent")
mode := flag.String("mode", "simple", "Ask mode when --ask is set (simple or plan)")
flag.Parse()
if *mode != "simple" && *mode != "plan" {
fmt.Fprintln(os.Stderr, "--mode must be simple or plan")
os.Exit(2)
}
base, token, org := envOr("STACKGEN_URL"), envOr("STACKGEN_TOKEN"), envOr("STACKGEN_WORKSPACE_ID", "STACKGEN_PROJECT")
if base == "" || token == "" || org == "" {
fmt.Fprintln(os.Stderr, "set STACKGEN_URL, STACKGEN_TOKEN, and STACKGEN_WORKSPACE_ID")
os.Exit(2)
}
gh := envOr("GH_TOKEN", "GITHUB_TOKEN")
if gh == "" {
fmt.Fprintln(os.Stderr, "set GH_TOKEN or GITHUB_TOKEN")
os.Exit(2)
}
var buf [4]byte
_, _ = rand.Read(buf[:])
suffix := hex.EncodeToString(buf[:])
integrationName := "sdk-int-" + suffix
agentName := "sdk-agent-" + suffix
client, err := stackgen.NewClient(stackgen.Config{BaseURL: base, APIToken: token, WorkspaceID: org})
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx := context.Background()
enabled := true
result, err := client.UpsertIntegrationWithSecretsContext(ctx, stackgen.UpsertIntegrationWithSecretsOptions{
Name: integrationName,
Type: "github",
Secrets: map[string]string{"token": gh},
Description: "GitHub for ops bot",
Enabled: &enabled,
})
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("secret_ids=%v integration=%v\n", result.SecretIDs, result.Integration["name"])
if _, err := client.Aiden.RegisterAgent(ctx, map[string]any{
"name": agentName,
"persona": "Help with GitHub tasks.",
"integrations": []string{integrationName},
"hitl": map[string]any{"always_allowed": []string{"*"}},
}); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("registered agent %s\n", agentName)
if !*ask {
return
}
started, err := client.Aiden.StartAsk(ctx, "Summarize open PRs", aiden.StartAskOptions{
EntityRefs: []aiden.EntityRef{{Name: agentName, Type: "agent"}},
Mode: *mode,
})
if err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
fmt.Printf("trace_id=%s session_id=%s\n", started.TraceID, started.SessionID)
}