Ask → export report (complete)
When the workflow does not always write session-report.md, export the session
write-up with export_session. This page embeds the full CLI from
examples/ask/ask-export-report.py.
How to run
pip install stackgen-sdk
# or editable: pip install -e ./clients/python
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_PROJECT=<project-uuid>
python examples/ask/ask-export-report.py \
--workflow incident-triage \
--message 'Investigate the checkout CPU alert in prod' \
--start-only
python examples/ask/ask-export-report.py \
--workflow incident-triage \
--message 'Investigate the checkout CPU alert in prod'npm install stackgen-sdk tsx
export STACKGEN_URL=https://app.stackgen.com
export STACKGEN_TOKEN=stackgen_…
export STACKGEN_PROJECT=<project-uuid>
# TypeScript uses the async Ask worker recipe (same start → wait → export path).
npx tsx examples/recipes/ask-async-worker.ts --start-only
npx tsx examples/recipes/ask-async-worker.tsComplete script
#!/usr/bin/env python3
"""Ask Guild → poll execution → fetch write-up (export or artifact).
Proves the free-text automation path: start a pinned workflow, wait until
settled, then pull the session report via export_session (fallback when
session-report.md is missing).
Documented at:
https://appcd-dev.github.io/stackgen-sdk/code-samples/automation-surface/
Credentials (flags override env):
STACKGEN_URL, STACKGEN_TOKEN, STACKGEN_PROJECT
(STACKGEN_PROJECT_ID is accepted as an alias for STACKGEN_PROJECT)
Usage:
pip install -e ./clients/python
python examples/ask/ask-export-report.py \\
--workflow incident-triage \\
--message 'Investigate the checkout CPU alert in prod' \\
--start-only
python examples/ask/ask-export-report.py \\
--workflow incident-triage \\
--message 'Investigate the checkout CPU alert in prod'
"""
from __future__ import annotations
import argparse
import json
import os
import sys
from pathlib import Path
from stackgen import StackgenClient, StackgenConfig
from stackgen.errors import StackgenError
def _org_id(cli_value: str | None) -> str:
return (
(cli_value or "").strip()
or os.environ.get("STACKGEN_PROJECT", "").strip()
or os.environ.get("STACKGEN_PROJECT_ID", "").strip()
)
def _config(args: argparse.Namespace) -> StackgenConfig:
base = (args.base_url or os.environ.get("STACKGEN_URL", "")).strip()
token = (args.api_token or os.environ.get("STACKGEN_TOKEN", "")).strip()
org = _org_id(args.org_id)
if not base or not token or not org:
raise SystemExit(
"set --base-url/--api-token/--org-id or "
"STACKGEN_URL / STACKGEN_TOKEN / STACKGEN_PROJECT"
)
return StackgenConfig(
base_url=base,
api_token=token,
org_id=org,
timeout_seconds=args.timeout_seconds,
poll_interval_seconds=args.poll_interval_seconds,
artifact_name=args.artifact_name,
output_path=args.output_path,
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", default="")
parser.add_argument("--api-token", default="")
parser.add_argument("--org-id", default="")
parser.add_argument(
"--workflow",
default=os.environ.get("STACKGEN_WORKFLOW", "incident-triage"),
help="Workflow name for entity_refs pin (env STACKGEN_WORKFLOW)",
)
parser.add_argument(
"--message",
default="Investigate the checkout CPU alert in prod",
)
parser.add_argument("--correlation-id", default="corr-123")
parser.add_argument("--incident-id", default="INC-1001")
parser.add_argument("--timeout-seconds", type=int, default=1800)
parser.add_argument("--poll-interval-seconds", type=int, default=5)
parser.add_argument(
"--start-only",
action="store_true",
help="Print start payload and exit (smoke test)",
)
parser.add_argument(
"--try-artifact",
action="store_true",
help="Try downloading session-report.md before export_session",
)
parser.add_argument("--artifact-name", default="session-report.md")
parser.add_argument("--output-path", default="session-report.md")
args = parser.parse_args()
client = StackgenClient(_config(args))
metadata = {
"correlation_id": args.correlation_id,
"incident_id": args.incident_id,
}
start = client.aiden.start_ask(
args.message,
entity_refs=[{"name": args.workflow, "type": "workflow"}],
source_metadata=metadata,
)
print("start:", json.dumps(start, indent=2, default=str))
if args.start_only:
return 0
trace_id = start.get("trace_id")
if not trace_id:
print("error: start response missing trace_id", file=sys.stderr)
return 1
trace = client.aiden.wait_for_execution(str(trace_id))
execution = trace.get("execution") or {}
session_id = start.get("session_id") or trace.get("session_id")
print(
f"settled status={execution.get('status')} "
f"trace_settled={execution.get('trace_settled')} "
f"session_id={session_id}",
file=sys.stderr,
)
if not session_id:
print("error: no session_id after wait", file=sys.stderr)
return 1
detail = client.aiden.get_session(str(session_id))
print("get_session:", json.dumps(detail, indent=2, default=str))
sessions = client.aiden.list_sessions(q=args.correlation_id, limit=5)
print("list_sessions:", json.dumps(sessions, indent=2, default=str))
if args.try_artifact:
dest = Path(args.output_path)
try:
path = client.aiden.download_session_artifact(
str(session_id),
args.artifact_name,
str(dest),
)
print(f"artifact_saved={path}", file=sys.stderr)
return 0
except StackgenError as exc:
print(f"artifact miss ({exc}); falling back to export_session", file=sys.stderr)
export = client.aiden.export_session(str(session_id), parts="report")
print("export:", json.dumps(export, indent=2, default=str))
parts = export.get("parts") or []
if not any(isinstance(p, dict) and p.get("kind") == "report" for p in parts):
print("error: export missing report part", file=sys.stderr)
return 1
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except StackgenError as exc:
print(f"error: {exc}", file=sys.stderr)
raise SystemExit(1)#!/usr/bin/env npx tsx
/**
* Async Ask worker — fire-and-forget start + separate follow-up.
*
* Submit on one process; poll from another with getRunStatus (never blocks for
* the full investigation). Sample labels are fictional Acme Corp ids.
*
* npm install stackgen-sdk tsx
* export STACKGEN_URL=https://app.stackgen.com
* export STACKGEN_TOKEN=stackgen_…
* export STACKGEN_PROJECT=<project-uuid>
*
* npx tsx ask-async-worker.ts --start-only
* npx tsx ask-async-worker.ts --follow-up --session-id <uuid> --trace-id <id>
*/
import {
RunPhase,
StackgenClient,
StackgenConfig,
StackgenError,
} from "stackgen-sdk";
import { mkdir } from "node:fs/promises";
import { join } from "node:path";
function env(...keys: string[]): string {
for (const key of keys) {
const value = (process.env[key] ?? "").trim();
if (value) {
return value;
}
}
return "";
}
function parseArgs(argv: string[]) {
const out = {
workflow: env("STACKGEN_WORKFLOW") || "acme-incident-triage",
message: "Investigate the Acme checkout CPU alert in prod",
correlationId: "acme-corr-123",
incidentId: "ACME-1001",
startOnly: false,
followUp: false,
sessionId: "",
traceId: "",
readyArtifact: "session-report.md",
downloadDir: "",
};
for (let i = 0; i < argv.length; i++) {
const a = argv[i];
if (a === "--start-only") out.startOnly = true;
else if (a === "--follow-up") out.followUp = true;
else if (a === "--workflow") out.workflow = argv[++i] ?? out.workflow;
else if (a === "--message") out.message = argv[++i] ?? out.message;
else if (a === "--correlation-id") out.correlationId = argv[++i] ?? out.correlationId;
else if (a === "--incident-id") out.incidentId = argv[++i] ?? out.incidentId;
else if (a === "--session-id") out.sessionId = argv[++i] ?? out.sessionId;
else if (a === "--trace-id") out.traceId = argv[++i] ?? out.traceId;
else if (a === "--ready-artifact") out.readyArtifact = argv[++i] ?? out.readyArtifact;
else if (a === "--download-dir") out.downloadDir = argv[++i] ?? out.downloadDir;
}
return out;
}
async function main(): Promise<number> {
const args = parseArgs(process.argv.slice(2));
const baseUrl = env("STACKGEN_URL");
const apiToken = env("STACKGEN_TOKEN");
const orgId = env("STACKGEN_PROJECT", "STACKGEN_PROJECT_ID");
if (!baseUrl || !apiToken || !orgId) {
console.error("set STACKGEN_URL, STACKGEN_TOKEN, and STACKGEN_PROJECT");
return 1;
}
const client = new StackgenClient(
new StackgenConfig({
baseUrl,
apiToken,
orgId,
}),
);
if (args.followUp) {
if (!args.sessionId && !args.traceId) {
console.error("error: --follow-up requires --session-id and/or --trace-id");
return 1;
}
const status = await client.aiden.getRunStatus({
sessionId: args.sessionId || undefined,
traceId: args.traceId || undefined,
readyArtifact: args.readyArtifact || undefined,
});
console.log(
JSON.stringify(
{
phase: status.phase,
session_id: status.sessionId,
trace_id: status.traceId,
run_id: status.runId,
execution_status: status.executionStatus,
session_status: status.sessionStatus,
artifact_names: status.artifactNames,
ready: status.ready,
ready_artifact: status.readyArtifact,
},
null,
2,
),
);
if (status.phase === RunPhase.Error) return 1;
let readyOk = true;
if (args.readyArtifact) readyOk = status.ready;
else if (
status.phase === RunPhase.Pending ||
status.phase === RunPhase.Running ||
status.phase === RunPhase.Unknown
) {
readyOk = false;
}
if (readyOk && args.downloadDir && status.sessionId) {
await mkdir(args.downloadDir, { recursive: true });
const name = args.readyArtifact || "session-report.md";
if (status.artifactNames.includes(name)) {
const dest = join(args.downloadDir, name);
await client.aiden.downloadSessionArtifact(status.sessionId, name, dest);
console.error(`downloaded ${dest}`);
}
}
return readyOk ? 0 : 2;
}
const start = await client.aiden.startAsk(args.message, {
entityRefs: [{ name: args.workflow, type: "workflow" }],
sourceApp: "acme-automation",
sourceMetadata: {
correlation_id: args.correlationId,
incident_id: args.incidentId,
},
});
const payload = {
session_id: (start as { session_id?: string }).session_id ?? null,
trace_id: (start as { trace_id?: string }).trace_id ?? null,
run_id: (start as { run_id?: string }).run_id ?? null,
workflow: args.workflow,
correlation_id: args.correlationId,
incident_id: args.incidentId,
};
console.log(JSON.stringify(payload, null, 2));
if (!payload.session_id && !payload.trace_id) {
console.error("error: start missing session_id and trace_id");
return 1;
}
return 0;
}
main().catch((err) => {
const msg = err instanceof StackgenError ? err.message : String(err);
console.error(`error: ${msg}`);
process.exit(1);
});Related: Async Ask worker, Ask → artifact, Automation surface.