Repository navigation
feat(ai-studio): add agent-harness node for external coding-agent delegation - #148
tbrandenburg wants to merge 7 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. |
|
Thanks for this work. We run Workflow Builder on Temporal with long shell-backed steps (10–25 min each: test runs, an APK build, coding-agent calls), and we hit the same two problems your spike found: with only Would it be possible to split the We are porting just that part into our copy in the meantime and are happy to share what we learn from running it. |
|
@ahmedibrahim085 Thanks for sharing your use case — that split makes sense. I’ve opened #201 with the optional |
|
I have updated the branch against current |

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.