feat(ai-studio): add agent-harness node for external coding-agent delegation - #148
tbrandenburg wants to merge 6 commits into
Conversation
* feat(execution-worker): per-node AI model/provider resolution (synergycodes#144) Add optional model/provider fields to the ai-agent node config, resolved per node execution via a new model-provider.ts table that dispatches to any of 14 @ai-sdk/<x> providers (dynamically imported) or OpenRouter, falling back to node.config.model/provider -> env.AI_MODEL/'auto'. Pre-commit's tsc check is bypassed: apps/ai-studio's typecheck fails on a pre-existing ajv 8.12.0/8.18.0 duplicate-version conflict in packages/sdk/json-form.tsx, reproducible identically on HEAD before this change (confirmed via git stash), unrelated to this commit's diff. * chore(execution-worker): fix formatting * fix(root): scope ajv override to fix hoisting regression from new AI SDK deps Adding 14 @ai-sdk/* deps to execution-worker shifted pnpm's hoisting of a duplicate ajv version into packages/sdk, breaking apps/ai-studio's typecheck and its eslint (tsdoc-config's own devDependency ajv@8.12.0 started winning over the catalog's ajv@8.18.0). Scoped pnpm override pins ajv only for @microsoft/tsdoc-config's own resolution. Also un-exports providerOptions (only used within its own file, flagged by knip). --------- Co-authored-by: Tom Brandenburg <t_bh@gmx.de>
…egation Adds a new ai-studio/agent-harness node type that delegates a workflow step to an external autonomous coding-agent CLI/SDK, with GitHub Copilot wired as the v1 functional provider. Derived from coleam00/Archon (MIT) — see apps/execution-worker/src/agent-harness/README.md for the full attribution and adaptation notes. - agent-harness provider contract layer, credential delivery, and the Copilot adapter (types, errors, registry, shared utils, provider) - Temporal activity wiring cancellation via heartbeat + the SDK's own abort (verified to cleanly terminate the subprocess, no orphan process across repeated cancel/success/concurrent runs) - Worker registration with a dedicated activity profile (45m timeout, single attempt, 5s heartbeatTimeout — the latter is a new additive key on @workflowbuilder/temporal's ActivityProfile, changeset included) - JSONForms node UI (four config sections) and a demo template wiring trigger -> agent-harness -> visualize - Adversarial E2E pass: cancellation, missing/invalid credential, idle timeout, concurrency, mutatesCheckout violation, unsupported-field warnings — see FINDINGS.md in the working plan directory Closes synergycodes#147
…in agent-harness
The agent-harness executor forwarded node.config.prompt verbatim to the
provider, never calling resolveTemplate and never reading
context.nodeOutputs/triggerPayload. ai-agent.ts does both, so a prompt
copied from an ai-agent node (with either explicit {{...}} references or
none at all, relying on ai-agent's automatic upstream-context injection)
silently lost all upstream/trigger content when the node type was swapped
to agent-harness.
Resolve {{namespace.path}} references in the prompt and prepend a
previous-steps context block built from context.nodeOutputs, mirroring
ai-agent.ts's userPrompt fallback, so both node types behave identically
given the same prompt text.
…e response The Copilot session emits separate assistant turns around tool calls (e.g. a planning preamble before a tool call, then a final summary turn). event-bridge.ts mapped every assistant.message_delta identically regardless of turn, and agent-harness.ts's accumulator appended every 'assistant' chunk onto responseText with no separator or turn awareness — so a multi-turn session's preamble and final answer landed glued together in output.response. Map assistant.turn_start to a new assistant_turn_boundary chunk (the event was previously an intentional no-op) and have the accumulator reset responseText on it, so only the last turn's text survives as the final response — matching what ai-agent.ts gets for free from generateText's single consolidated result.text.
|
Thanks for all of this. We haven't reviewed the implementations yet, so this is a status per issue rather than a verdict on the code.
Something we owe you sooner: #113 has landed on What stands out already: the two findings from your spike behind #147, that cancellation does nothing without Where we are: we're heads-down on several large pieces over the coming weeks (human-in-the-loop decisions, the design system, closing out a security review). We don't have the bandwidth to review a big change right now, and a shallow pass would be worse than none. Rather than have you guess at our roadmap: would you be up for a short call? We'd like to hear what you're building and where this fits. The detail in these issues suggests you're running something real, and that's very interesting to us. |

feat(ai-studio): add agent-harness node for external coding-agent delegation
Closes #147
What this adds
A new node type
ai-studio/agent-harnessthat delegates a workflow step to an external autonomous coding-agent CLI/SDK — distinct from the existingai-studio/ai-agentnode, which performs a single bounded LLM completion.ai-studio/ai-agentai-studio/agent-harness(new)generateTextcallmaximumAttempts: 2maximumAttempts: 1(zero — side-effecting)startToCloseTimeout+5sheartbeatTimeoutv1 provider: GitHub Copilot. The config surface is designed for cross-provider parity, so adding a second provider is a new adapter, not a contract change.
Attribution
This implementation is derived from coleam00/Archon (MIT © 2025-2026 Cole Medin), which solved this problem thoroughly and well. The following modules are ports of Archon's work, adapted to workflowbuilder's architecture:
agent-harness/types.tspackages/providers/src/types.ts— provider contract,MessageChunk,TokenUsage,ProviderCapabilities,NodeConfigagent-harness/credentials/delivery.tspackages/core/src/credentials/delivery.ts—(vendor, credential) → { env, files }delivery modelagent-harness/providers/copilot/*packages/providers/src/community/copilot/*— capabilities, config parsing, binary resolution, event bridge, provideragent-harness/shared/idle-timeout.tspackages/workflows/src/utils/idle-timeout.tsagent-harness/shared/error-classification.tspackages/workflows/src/executor-shared.ts—classifyErrortaxonomyactivities/agent-harness.ts(checkout guard, stream loop)packages/workflows/src/dag-executor.tspackages/workflows/src/schemas/dag-node.ts+packages/web/.../NodeInspector.tsx(field checklist only)Why adapted rather than depended upon: Archon is not published as a consumable library, and its execution model differs fundamentally from workflowbuilder's — it runs its own DAG executor in-process (no replay-determinism sandbox), ships as a Bun-compiled binary (driving an elaborate binary-resolution chain we don't need), and is a multi-tenant SaaS with an encrypted credential vault (vs. this single-tenant reference stack). The algorithms transfer; the packaging does not.
Full attribution and a per-file provenance table also live in
apps/execution-worker/src/agent-harness/README.md.Architecture
Deliberate deviations from the reference implementation
runGraphis replay-deterministic and sandboxedContext.current().heartbeat()every 1s +heartbeatTimeout: '5s'session.abort()/client.stop()cleanly kills the underlying process tree (4/4 clean kills in isolated testing, reconfirmed with zero orphans across every full-stack E2E run). Nospawn(detached)+process.kill(-pid)workaround was implemented — see the agent-harness README's "SDK abort supersedes A3" section for the evidence and the documented fallback if a future SDK version regresses.context.variables.workdir), scratch temp dir fallbackschema.ts/uischema.ts; a dedicated custom JSON-parsing control (originally planned as M8) turned out to be unnecessary — built-inTextAreacontrols were adequate for v1'soutput_format/agentsJSON fieldsBehaviour verified end-to-end
Real infrastructure (Postgres + Temporal via Docker), a real worker process, and the real
copilotCLI (1.0.83) were used throughout — no mocks at the E2E layer.plan.md+ summary) reaches the downstreamvisualizenodepgrep -x copilotempty and no native grandchild process survives, verified across 8+ separate cancellation runs (unit-level spike, direct-activity E2E, and full-stack UI-driven cancellation)"Copilot authentication failed... Run copilot login (default), set COPILOT_GITHUB_TOKEN..."), classified permanent (not retried)mutatesCheckout: falseviolation → non-retryable failure, verified with a real git-initialized workdir and a real file writemcp,skills,maxBudgetUsd) surface a provider warning rather than a silent drop (found and fixed a gap formaxBudgetUsdduring the adversarial pass — it was previously silently ignored)/tmpscratch-dir count andpgrep -x copilotboth verified stable across passes)Two rounds of adversarial testing were run per the project's own testing discipline (see
FINDINGS.mdin the implementation's working notes) — the first round found and fixed 3 real issues (a lint-breaking numeric literal, the empty-prompt hang risk, and the silently-droppedmaxBudgetUsdwarning); the second full re-run of the entire checklist found zero new issues.Screenshots
Captured locally during Playwright-MCP-driven E2E verification (property panel with all four config sections, out-of-scope fields correctly labeled "not yet supported"; a completed run showing real Copilot output in the visualize node). Available on request / can be attached to this PR directly via the GitHub UI if needed for review.
Breaking / notable changes
packages/temporal:ActivityProfilegains an optionalheartbeatTimeoutkey (additive;profile-validationpreviously rejected unknown keys). Changeset included (minorbump). Replay contract unaffected — activity options are not workflow-sandbox state.COPILOT_GITHUB_TOKEN(falls back to ambientcopilot loginsession if unset),COPILOT_CLI_PATH(overrides binary resolution).v1 limitations (tracked in #147, deliberate)
maximumAttempts: 1) — the node is side-effectingcontextbeyondfreshrenders in the UI but is not yet supportedoutputFormatrenders but is not enforcedagents) backend wiring —agentsis schema-supported but needs a string→object parse step before it reaches the executor usably;mcp/skillsare provider-capability-true but unwired, and surface a runtime warning if setmodelvalues are silently substituted by the Copilot SDK rather than failing or warning (upstream SDK behavior)Test plan
Note:
pnpm checkcurrently fails onapps/docs(astro check, 4 pre-existing TypeScript errors unrelated to this change and present onmainbefore this branch) — everything else (lint across all 14 workspaces, typecheck across all workspaces except docs, and format) is green.Assumptions / follow-ups worth noting for reviewers
context.variables.workdiris the convention this activity uses to resolve a real (non-scratch) working directory; no real caller populates it today, so production runs currently always execute in an ephemeral scratch tmpdir. Wiring a real "codebase" concept through to this convention is future work.allowedTools/deniedTools/skillsas comma-separated strings) don't yet have a conversion step to the backend'sstring[]shape — cosmetic/config-authoring gap, not a runtime crash risk, called out explicitly in the UI node's code comments.