skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
eliasstravik/gtm-skills320 installs

gtm-workflow

Triggers when a user asks to build, create, update, run, test, schedule, deploy, host, upgrade, inspect, open the inspection UI, or delete a saved GTM workflow, its result table, runs, or diagram in a GTM workspace, with phrasings like "build a workflow that scores our inbound companies", "run Score inbound accounts", "put it on a weekly schedule, hosted", "enrich my network, connections, or followers and their companies", "upgrade the workflow runtime", "take our workflows live on Vercel", "connect the workflow project", "set up keys", or "share this workflow's data". Owns the workflows folder, which holds workflow code, tables, runs, schedules, diagrams, keys and share links, and the Vercel project it deploys to, including connecting a GTM agent to it. Not for the workspace, ICPs, or personas themselves (gtm-workspace, gtm-icp, gtm-persona), or one-off fit checks that are not saved (gtm-qualify-prospects).

How do I install this agent skill?

npx skills add https://github.com/eliasstravik/gtm-skills --skill gtm-workflow
view source ↗

Is this agent skill safe to install?

  • Gen Agent Trust Hubpass

    The gtm-workflow skill is a legitimate framework for managing and deploying Go-To-Market workflows to Vercel and local environments. It facilitates data enrichment through external providers like Monid and Blitz, handles local Postgres database management via the embedded-postgres package, and provides a web UI for workflow monitoring. Security is maintained through robust SSRF protection in its web fetcher, secure handling of secrets using environment variables, and the use of safe process spawning patterns to prevent command injection.

  • Socketwarn

    2 alerts: gptAnomaly

  • Snykwarn

    Risk: unknown · No issues

What does this agent skill do?

GTM Workflow

Trigger

Apply this skill when a request concerns a saved workflow: creating, changing, running, scheduling, deploying, upgrading, inspecting, opening its UI, or deleting one, or its table, runs, or diagram.

When creating or changing a workflow, check recipes (e.g. network enrichment) for one that matches the request and follow it; this skill still owns the lifecycle. Ordinary runs of an unchanged workflow need no recipe.

Show the intent menu only when the user's entire request is /gtm-workflow or /gtm-workflows, with no action or other words. Ask Choose a workflow action. with three choices: Open GTM Workflows, Create a workflow, Manage a workflow, and wait. Manage asks which workflow and action: change, run/test, schedule/deploy, upgrade/repair, or delete. Bare entry performs no creation, execution or deployment.

Open GTM Workflows is already an explicit Open request. Resolve the verified viewer URL and return it immediately using entry links; do not show the intent menu or ask the user to choose Open again. Loading this skill does not turn an explicit request into a bare invocation. Other explicit actions also go directly to their procedure. If Open finds no workspace, explain workspace setup; if the workspace has no workflows, show its empty list.

Scope

This skill owns <workspace>/workflows/, a Vercel Workflow runtime on Nitro copied from templates/ on first use: workflow files, db/tables/, migrations, vercel.json, .env, the local database, runs, diagram pages, and the deployed copy. Runs execute locally against this workspace's own Postgres in data/pg by default; they target the deployed copy when the user asks or the host states there are no local runs, and hosted results are read through the deployed copy's query route. Keys live in workflows/.env.local locally and in the project's Production variables when deployed, both edited through the viewer's Keys page (Connections tab); for setup, key discovery and key changes, read Keys. The remaining workspace files outside workflows/ belong to the other gtm skills.

Inputs

The request; the workspace found by the contract; the ICPs and personas the workflow reads by slug; references/local.md for commands, keys, and verified facts; references/deploy.md for hosting.

Roles

The user's request authorizes routine code changes and real runs within the configured row and cost caps. State the estimate and proceed; confirmation is reserved for destructive actions under the shared interaction rules. The workflow project goes live through shared setup --deploy (deploy.md), run directly or as the last step of optional gtm-agent setup; the user chooses the AI backend once per workspace in .env; the agent writes, runs, and reports. Deploy is the push.

Procedure

When persisting enriched people or companies, follow shared profiles for storage, identity, freshness, paid attempts and scoped Data views. Its two phase workers and common budget replace the generic self-fan-out pattern below for multi-phase enrichment. The profile tables are runtime tables in schema gtm; scripts/migrate.mjs migrates them with everything else, and upgrades preserve them.

The first gtm skill used in a conversation checks once for a newer release, as updates says. Talk by the six rules in interaction; reproduce the dialogues. Workflow slugs follow the contract's slug rule; the display name is the doc comment's first line.

JobDo
CreateFirst use on a personal computer: run shared local setup from Keys, which creates an empty registry and prepares the local database without execution. Hosted authoring uses the already configured runtime. Start npm run dev (the one local command: viewer, Keys page and runs; opening it runs nothing). Ask where the rows come from. When the workflow reaches people (an approval on a stage, or a notify call), ask which Slack channel it should post in, one question: a channel id (C0…, shown at the bottom of the channel's About tab) or "here", which means the channel of this conversation when the agent knows its id; the answer becomes a NOTIFY constant at the top of the workflow file, passed as notify to every stage and notify call, never a project variable. There is no per-workflow backend question: agent stages read GTM_AGENT_BACKEND from .env at run time (claude or codex for the author's subscription on a personal computer, unset for AI Gateway), so one file runs on the subscription locally and on the Gateway hosted. At scaffold on a personal computer, when claude or codex is on PATH and no Gateway key is present, set it once and say so; otherwise leave it unset. Author literal viewer.businessGraph and viewer.description (about 280 characters, at most 500) using viewer, with labels and branches checked against the new code. Write workflows/<slug>.ts from example-scores.ts, or from example-research.ts when it has an agent stage, its table in db/tables/<name>.ts, register both in workflows/index.ts and db/tables/index.ts (keep the example workflows and their tables; removing a table in the same generate as adding one makes the generator ask a rename question that no host can answer), run npm run db:generate -- --name <name>, run npm run viewer:register once to assign the new workflow identity, rebuild, open its Diagram page, commit, close with what was created and the Open GTM Workflows entry. On a host without local runs: no localhost link, ever; after the push, read GET /api/link/<slug> on the deployed copy every 10 seconds, up to 3 minutes, until git merge-base --is-ancestor HEAD <commit> holds for the commit it returns, and only then post its verified viewerUrl; a reply written before that holds carries no links and says the deploy is under way. When the user asks to run, execute the requested scope after checking keys and caps, stating the estimate without another confirmation; when no connected project exists on a host without local runs, close instead by naming the connection steps in deploy.md.
UpdateChange the workflow, its viewer.businessGraph and its viewer.description together, including any new business decision branches and owner details; preserve viewer.id, validate the metadata with node scripts/build-viewer.mjs, move an export const criteria copy to readIcp/readPersona (see Criteria below), add columns (then db:generate; a running npm run dev applies it); preserve existing data; close with what changed and the Open GTM Workflows entry.
RunTarget: local by default on a personal computer; the deployed copy when the user asks or the host states there are no local runs, which needs a connected project and, first, Deploy's readiness check. Pre-run check: the row count, rows × estimate against both caps, every connection the workflow uses against the target inventory in Connections, and whether the workflow is new or changed since its last run. A requested run, whether one or many rows, proceeds without a permission or test-first question: state the row count and estimated cost, then run the requested scope. A missing key or exceeded cap stops execution and names the blocker. Start through the route, poll the read route, close with the result in plain words: done, failed, skipped, cost; no links, unless they changed since the thread last showed them. When the read route lists a pending approval, stop polling and ask the person in one message (the tool, its input in plain words, the price when known), then decide it through the approve route with their answer and its reason, and resume polling; a run with stream on may be narrated from the stream route while it runs. When a local run follows a hosted one, say it may re-spend on rows the hosted copy already did.
Open UIFor “Open GTM Workflows UI” or an explicit link request, follow entry links. Resolve the discussed workflow by stable identity; without one, open the list. Check readiness. Locally start npm run dev if needed; its Connections tab (<viewer origin>/connections, the address to give for keys) is the Keys page on the same server. Opening the UI never executes a workflow, runs a migration, or deploys a missing host. Report a concrete setup blocker when unavailable. Always return the entry when explicitly asked, even if already shown.
ShareHosted only: read grants using /api/viewer/service?v=3&workflow=<id>&op=grants. Show the selected tabs and the current permitted-data summary before an explicit save. POST saveLink with {views, policy, save}. policy is the displayed hash; save: true explicitly approves scope or policy changes. Copy without changes reuses the link. Default is Diagram with no expiry. POST revokeGrant with the active ID to turn it off. Re-enable creates a fresh secret. Use the exact returned URL. See viewer.
DeployDeploy is the push; follow deploy.md for the one-time connection and the readiness check. A push never touches a run already in flight: it finishes on the code it started with. The hosted copy runs every agent stage through the Gateway whatever .env says locally.
UpgradeFirst run node scripts/upgrade-package.mjs <workflows> from this skill's folder (a preview, it writes nothing): when this skill's template is older than the workspace it refuses, and the upgrade stops there with "the skills are behind this workspace; update them first", never a downgrade. Compare workflows/ to the installed templates/; say what differs; a workspace from before the Postgres runtime (it has db/tables/cache.ts) is not upgraded by this job: it is migrated as updates says, after the user agrees; update template-owned lib/, server/, viewer/, connections-ui/, share-server/ (delete a leftover viewer-server/), scripts/, drizzle-runtime/, nitro.config.ts, drizzle.config.ts, drizzle-runtime.config.ts, tsconfig.json, share-firewall.json; copy skills/ from the template when the workspace has none; update the workspace root's .gitignore and .github/workflows/check.yml from root/, keeping CI steps the workspace added; follow runtime reliability to merge dependencies and startup scripts into package.json, preserving user-added dependencies and custom commands, then copy the template's package-lock.json over the workspace's, delete node_modules, run npm install and confirm with npm ci --dry-run (installing over the old lockfile drops other platforms' packages and breaks the hosted build, and a lockfile generated from nothing is rejected by npm ci); regenerate workflows/index.ts, db/tables/index.ts, and skills/index.ts, preserving each workflow entry's viewer, data, intake, and other authored properties, including imported metadata files; never touch other files in workflows/, db/tables/, skills/, drizzle/, .env, data/; vercel.json is template-owned except its crons, which shared setup merges; merge generated-asset and .env.* ignore entries (including lib/criteria.generated.ts); run shared setup --local as Keys specifies; run npm run viewer:register only for entries missing an identity; build, start npm run dev once so the local database is migrated, then verify the hosted private/share builds. Read each existing workflow and add or correct its businessGraph and description before the build; when one carries export const criteria or ICP or persona rules written into its code, move it to readIcp/readPersona under Criteria, and say which workflows changed. Preserve all workflow IDs. Restart the local server through npm run dev and verify its startup settings and a diagram page; hosted upgrades verify the deployed copy after the readiness check and rerun hosted setup, which re-applies the share rate limits.
DeleteRemove the workflow file, its registry entry, and its vercel.json cron; keep the table and its data and say so; when a project is connected, say the hosted copy drops it and its schedule as the save deploys.

For Create, Update and Upgrade, read viewer. Keep a valid business diagram and, for every Data tab, a valid viewer.sharePolicy. Build before saving; missing or mismatched sharing metadata is an authoring error. Preparing a policy makes Data selectable; changing a public link's access still requires an explicit sharing request.

Every save: one sentence on what will change, pull first when origin/main exists, edit through the host's write path, commit on main with a plain-language message, push when a remote exists, verify the commit (and that it reached origin/main when a remote exists), close with what was created, changed, or deleted.

On a host that states there are no local runs, the values live in the host's environment (the scaffold never ships env.example, on any host); every run targets the deployed copy after the push and after Deploy's readiness check; Upgrade verifies through the deployed diagram page after that check instead of restarting a server. Two rules hold on such a host whatever else the agent does:

  • Links: a dev server or a local build may be started as a check, but nothing it serves is ever posted. The only links a reply may carry are the verified viewerUrl the deployed copy's link route returned after the readiness wait; localhost never appears in a reply.
  • Keys: read GET /api/connections through the protected workflow transport for presence and declared usage, including unused services. Use the saved Note as the service name and the exact variable for code, following Connections naming. Direct missing-key entry to the verified connectionsUrl from the private link route. Never collect keys in chat. Saved changes become active after a deliberate deployment; presence does not prove validity.

Conventions the code follows

  • Linked result tables: linked data defines the optional data registry property and the read-only People/Companies-style viewer. Register it when users need to browse related records. Its private Data link comes from the same link route. Preserve viewer, data, and intake during upgrades. Sharing is an explicit private-viewer action; it requires a versioned table/column/relationship/row policy described in viewer.

  • Step choice: every step is the plainest kind that does the job. Deterministic work (parsing, arithmetic, a fetch, a provider or gateway API call with known inputs, a Slack post) is a plain "use step" function with no model. Judgment over text the workflow already holds (scoring, classifying, extracting, drafting) is a single-shot AI step, generateObject with a schema. An agent stage (runAgent) only when the user names an agent, or the work needs the model to choose its own tool calls as it goes (find the right endpoint, research across pages) and no sequence of plain and AI steps can be written in advance; a stage that would always call the same tools in the same order is those steps instead. When the user asks for an agent where steps would do, say so in one sentence and build what they asked.

  • Rows come from defaultInput (inline), a provider or API call inside a "use step" function, a table another workflow fills, or an inbound webhook (export const intake = defineIntake({ secretEnv, signature, eventId, toRow }) from lib/intake.ts, registered as intake in workflows/index.ts; POST /api/intake/<slug> verifies the sender's signature over the raw body, drops redeliveries by event id for 30 days, and starts one run with the mapped row; senders post to the public share project, which relays signed events past Vercel Authentication; after building or editing one, always tell the user the sender setup in deploy.md); never from a workspace file or local-only data. Input is RowsInput from lib/rows.ts, { rows?, maxRows?, maxSpendUsd? }, each row an object with a required string key, the table's primary key; the workflow's constants are the defaults; the start route merges a POST body over defaultInput, so the limited run is POST { maxRows: 1 }.

  • runRows({ rows, table, step, maxRows, maxSpendUsd, estimateUsd, freshForMs, concurrency?, saveEvery?, fanOut? }) from lib/rows.ts is the loop; step(row) is a plain async function in workflow scope that awaits "use step" functions in sequence and returns { ...columns, costUsd }. Tables are passed by name and resolved through db/tables/index.ts. Finished rows are saved ten at a time in one step (saveEvery), because every step costs a hop of the engine; a crash before a save loses nothing, the engine replays finished steps. Every workflow passes fanOut: { workflow: <its own function>, input, chunkSize }: above that many rows the run only splits the list into child runs of itself, four at a time (each wave started in one step), each with its exact share of the caps, and adds up their totals, so a list of any size is one run with one result. chunkSize is 100 for a workflow of plain steps and 20 for one with an agent stage: the engine caps a run at 25,000 events and an agent row costs about 100 of them. Cancelling the parent cancels its children.

  • Every result table has key, updated_at, cost_usd, error. All database I/O, every cached() and generateObject call happen inside "use step" functions declared as async function name() { "use step"; }, never arrow or method form; every paid or AI step sets name.maxRetries = 0.

  • Reads leave the database once and small: every byte Postgres sends to the app counts as Neon data transfer, so a read names its columns (db().select({ key: t.key, score: t.score }), never select() on a wide table; getProfile(db, entity, key, { columns }) for a profile), filtering, counting and aggregation run in SQL (count(*)::int, jsonb_array_elements, = ANY(...)), a step reads what it needs once and keeps it rather than reading a row again per item, a list paginates, and no step pulls a table into Node to loop over it. The budgets every read path is held to are in lib/read-budgets.ts; tests/read-budgets.test.ts in this skill enforces them, and local says how to measure a workflow's own reads before shipping it.

  • Agent stages: an agent inside a workflow is always runAgent() from lib/agent.ts, called from a plain async function in workflow scope (the stage function, an agent node), never WorkflowAgent directly and never inside a "use step" function; the helper makes every model call and tool call a step, races the time limit against sleep(), cleans the schemas, and returns real cost. Its config is the whole authoring surface: model and reasoning (leave model out: it defaults to gtmModel() from lib/models.ts, see the next rule; reasoning defaults to GTM_REASONING, else high), instructions, skills (names from skills/index.ts, each a text module under skills/), tools (mcp servers by name with url, keyEnv, allow, maxCalls; web.fetch free, a step; web.search executed by AI Gateway (true or "gateway", Exa, any model, no key) or by OpenAI ("openai", openai/* models only), inside the model call rather than as a step, so it cannot be approved or call-limited; custom tools whose execute is a "use step" function), approve (tool names requiring destructive-action confirmation or an explicitly requested workflow review; never add routine approval gates by default; the row waits on a hook and the run's read route lists the request until POST /api/runs/<id>/approve decides it; set timeout in days when approvals may wait), stream (every model and tool event on GET /api/runs/<id>/stream), maxSteps, maxUsd (soft: the agent stops using tools after the call that crosses it and writes up what it has), timeout (a duration string; the row fails), schema (or none for plain text), prompt or messages, and, for anything else WorkflowAgent takes, agent (constructor options: sampling, provider options, prepareStep, prepareCall, callbacks, telemetry, contexts) and call (per-call options: toolChoice, activeTools, transforms). Schemas use nullable fields, never optional ones, and no string formats (z.string(), not .url() or .email()); the description carries the intent. Never pass timeout: or AbortSignal.timeout() to the SDK; never build MCP clients or tools by hand. example-research.ts is the reference.

  • A single-shot AI step is generateObject({ model: gtmModel(), ... }) inside a step; anything that uses tools is a runAgent stage. Workflow code never names a model id or reads GTM_MODEL itself: lib/models.ts is the only place model ids live. gtmModel() returns GTM_MODEL when the user chose a model, else the default that follows GTM_AGENT_BACKEND: claude gives the newest Claude Haiku, codex or anything else GPT-6 Luna, so the hosted copy stays with the author's local family. setup --deploy copies GTM_AGENT_BACKEND to Production, where it only picks that default. When the user names a model for the deployed copy, set GTM_MODEL on the Vercel project (Production), not in code; a per-stage model is only for a stage that truly needs a different model. The stage's backend is decided per run from the frozen environment: GTM_AGENT_BACKEND (claude or codex: the author's subscription, one step, this machine only; MCP servers and web tools carry over; codex needs each MCP server's allow list) or the Gateway when unset. A stage that uses custom tools, approve, stream, or messages, and every stage on the hosted copy, runs on the Gateway regardless. backend on a stage overrides the variable for that stage only; never write backend just to pick local or hosted, the variable does that. No other AI path exists.

  • Criteria: a workflow never copies an ICP or persona into its code (no title lists, size limits or rule text taken from the files). It reads them by slug when the run starts: const icp = await readIcp("<slug>") or readPersona("<slug>") from lib/criteria.ts, once in workflow scope before runRows, passing the result to the row step. Each returns { name, text, fields, signals, disqualifiers }: text is the file for an AI step's prompt; fields holds each stated field by its label (Unknown ones absent), items(fields["Job titles"]) splits a list; rule steps take titles, sizes, locations and disqualifiers from there. A rule step checks every stated field its rows can answer and names in the reason each stated field it could not check (a persona's Languages when rows carry no language), never skipping one silently. Local runs read the files each time; the build bakes them into lib/criteria.generated.ts (gitignored) for the deployed copy, so an ICP or persona edit reaches the next run there after its push deploys. Rows still inside freshForMs keep scores made under the old criteria. A missing file fails the run by name.

  • Doc comment: first line is the title, second paragraph the one-sentence summary; the diagram page falls back to the summary when viewer.description is missing.

  • Workflow description: on Create, Update and Upgrade write viewer.description, what the whole workflow does in plain language, about 280 characters and never over 500 (the build fails above 500); rewrite it when a change makes it untrue. See viewer.

  • Viewer metadata: author literal viewer.businessGraph with stable node IDs, human labels, input/action/decision/output kinds, explanations and meaningful decision edges. Keep provider/caching details in explicit owner fields. Review the diagram against the actual workflow code on Create, Update and Upgrade; preserve viewer.id. See viewer for the schema and validation.

  • Where to look: follow entry links after create/change, deploy/upgrade, ordinary runs, and explicit requests. Return the canonical private viewerUrl as Open GTM Workflows. This is separate from issuing an external share grant.

  • Connections: read the target inventory before writing a paid or AI step and in every pre-run check. Name a missing connection once and open the trusted Connections form; use its workflow-declared gateway relationship when deciding which key is required. Author literal viewer.connections alongside the business graph, one entry per key the code reads (exact variable), so the workflow's Connections tab can show each as set or missing. Follow Connections for unknown keys, activation state and safe credential entry.

  • Gateways: a paid step that uses a provider found through a gateway tool the host exposes (a data marketplace, an MCP server) calls that gateway from code with the gateway's own key, <GATEWAY>_API_KEY managed through Connections, never the provider behind it. When the gateway's HTTP API is unknown, read its documentation before writing the step; do not guess.

  • Engine features are used natively (see local.md): a hook or sleep is a wait node, a child workflow a sub node, a runAgent stage an agent node.

  • Reaching people: notify({ kind, text, approval?, target }) from lib/notify.ts posts to the GTM agent's notify route, which posts the text straight to Slack with the agent's bot token and no model call; tell and show are plain posts, ask and handoff open a message whose thread the agent watches, so a person's reply there wakes it. An approval in a stage notifies by itself when GTM_AGENT_URL and GTM_NOTIFY_SECRET are on the workflow project. Where it posts is code: const NOTIFY = { channelId: "C0…" } at the top of the workflow, given as notify: input.notify ?? NOTIFY to each stage and as target to each notify call; posts land top-level in that channel, never in the conversation a run was started from. Per-row news goes through runRows, never a notify call inside the step: notify: { target: input.notify ?? NOTIFY, every: "chunk", line: (row, columns) => … }, where every is row (one post per finished row), chunk (one post per run of rows, so one per child when fanned out; the default to propose) or run (one post with the totals at the end), a count and never a judgment; line returns a string, or { text, blocks } when a row deserves a rich post. notify: false on a stage keeps its approvals silent. The agent project's GTM_NOTIFY_CHANNEL is only the fallback when a workflow names none. A workflow calls the agent only to reach people, never to think.

  • Helpers are admitted to lib/ only when a bare SDK call was shown to fail or double-spend; everything else is a native SDK call with a worked example in patterns.

  • Routes (hosted: behind Vercel Authentication, reached with vercel curl from a laptop or the host's bypass from the hosted agent; locally no password, this computer only, and changes never from another web site): POST /api/run/<slug>, GET /api/run/<slug> (Vercel Cron only, CRON_SECRET; refused locally), POST /api/intake/<slug> (no bearer; the sender's signature; senders use the share project's relay), GET /api/runs/<id> (status, output, error, and approvals, pending first), POST /api/runs/<id>/approve with { token, approved, reason? }, GET /api/runs/<id>/stream (one JSON event per line while an agent stage streams), POST /api/runs/<id>/cancel, GET /api/link/<slug>. Base: http://localhost:3939 locally, GTM_WORKFLOW_URL for the deployed copy. When GTM_AGENT_HOSTED is 1, the host adds Vercel's protection bypass to every call outside the agent's reach; send no credential.

Outputs

workflows/<slug>.ts, its table and migration, registry entries, a vercel.json cron when scheduled, a diagram page, rows in the result table, and, once a project is connected, the deployed copy that every push refreshes.

For queue, provider, MCP, or CLI failures, read runtime reliability before attributing the cause or proposing a retry.

Exceptions

Requires the gtm-workspace skill installed alongside this one; when ../gtm-workspace/SKILL.md is missing, say: install it the same way this skill was installed, with npx skills add eliasstravik/gtm-skills -s gtm-workspace -y (add -g when this skill lives in the global skills directory), then retry. A missing key stops a run before it starts and is named. A production build on Vercel with no DATABASE_URL fails on purpose; report it and say the database is added through the Neon integration, as deploy.md describes. A run on the deployed copy needs a live project (a linked workflows/.vercel/project.json on a computer; GTM_WORKFLOW_URL on the hosted agent); without one, say how to take it live as Deploy does (shared setup --deploy on a computer with the CLIs signed in; gtm-agent is optional) instead. A workflows/ that contains scripts/gtm.ts is the previous runtime, which Upgrade does not touch: offer the migration in updates (set it aside, where the workspace's history keeps it and its deployed copy runs until the next push, then rebuild on the current runtime).

QC

  • Every workflow has a valid business graph whose labels, branches and results match its actual code. Business behavior changes update metadata in the same change. The metadata build passes; compiler graphs never substitute for missing business metadata.
  • Every step is the plainest kind that does its job: no AI step for deterministic work, no agent stage where plain and AI steps in a fixed order would do, unless the user named an agent.
  • A one-row test was offered, through the question tool, before the first run of more than one row of a new or changed workflow; a one-row request ran without that question; the cost statement preceded every run.
  • The result table has the four fixed columns; paid and AI steps have maxRetries = 0; no cached() or generateObject call sits in workflow scope.
  • Every agent stage is a runAgent() call in workflow scope with maxSteps, maxUsd, and timeout set; no WorkflowAgent import, no timeout: option, no AbortSignal.timeout, no .url() or .email() in a schema, no optional fields in an output schema; a new or changed agent stage was tested on one row and the reply named its tool calls, stop reason, and real cost.
  • Every workflow passes fanOut with itself, chunkSize 20 when it has an agent stage; a module a workflow imports never reaches the database outside a "use step" function; route-only code lives in *-api.ts files.
  • An ordinary Run reply ends with the result and repeats no entry unless its address changed or the user explicitly requested it; a save ends with what was created, changed, or deleted.

References

interactions dialogues; local and production for the native commands that reach production; imports for CSV imports, which are runs started with the file's rows; local.md for start, keys, data, schedules, tables, the viewer, the agent stage, fan-out, intake, notify, and the findings ledger; patterns for native SDK calls with worked examples; deploy.md; templates/workflows/example-scores.ts as the reference implementation and templates/workflows/example-research.ts as the agent-stage reference; from gtm-workspace: interaction, contract.

Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.

<a href="https://skillzs.dev/skills/eliasstravik/gtm-skills/gtm-workflow">View gtm-workflow on skillZs</a>