Skip to content

feat(sdk): applySiblingLinks — link sibling-repo packages during workflow setup#776

Merged
khaliqgant merged 3 commits intomainfrom
feat/sdk-sibling-links
Apr 24, 2026
Merged

feat(sdk): applySiblingLinks — link sibling-repo packages during workflow setup#776
khaliqgant merged 3 commits intomainfrom
feat/sdk-sibling-links

Conversation

@khaliqgant
Copy link
Copy Markdown
Member

@khaliqgant khaliqgant commented Apr 24, 2026

Summary

Adds a generic, language-agnostic helper applySiblingLinks to @agent-relay/sdk/workflows that workflow authors chain into their setup. When a workflow consumes a package that lives in a sibling repo on disk (common in our monorepo-of-repos setup), linking redirects the package at dev-time to the sibling's current build output.

Why

In a recent sage wire-up workflow (AgentWorkforce/sage#105), agents writing code against @agent-assistant/proactive hit a version mismatch: npm install resolved an older published version that didn't have the exports the workflow needed. Rather than stopping, agents reached for declare module "@agent-assistant/proactive" { ... } augmentations and ~200 lines of fallback implementations to keep their code compiling. Tests type-checked against the fiction, the 80-to-100 gate passed, the PR shipped code that couldn't integrate with the real package.

Root cause: agents only see node_modules at workflow-run time, not what the sibling repo actually exposes on disk. Preflight export checks (option A) and prompt rules forbidding augmentation (option B) catch symptoms. Linking removes the wall entirely — agents see the head-of-main interface, no fabrication needed.

Public API

import { workflow, applySiblingLinks } from '@agent-relay/sdk/workflows';

const wf = applySiblingLinks(workflow('my-feature').pattern('dag'), {
  dependsOn: ['install-deps'],
  links: [
    {
      name: '@agent-assistant/proactive',
      path: '../agent-assistant/packages/proactive',
      expect: ['recordSignal', 'drainSignals'],
    },
  ],
});

Auto-detects the sibling's manifest and dispatches:

Manifest Mechanism Modifies committed files?
package.json npm link (symlinks into node_modules) No
pyproject.toml / setup.py / setup.cfg uv pip install -e or pip install -e No

When expect is provided, the step runs a post-link smoke import (node --input-type=module -e for npm, python3 -c for python) and fails if any named export is missing. That's the final guardrail: even if the link command succeeded, if the real interface doesn't match what the workflow expects, we fail before an agent writes a single line.

MVP scope

  • npm + python. Auto-detected by manifest presence.
  • Fail-fast on: missing sibling path, unknown manifest, link command failure, missing expected export.
  • Other languages (Go replace directive, Cargo .cargo/config.toml [patch], Gradle includeBuild, Bundler bundle config local, .NET ProjectReference) land as new branches of the per-link dispatch behind the same applySiblingLinks API. None require public-API changes.

Test plan

  • 12 unit tests: manifest detection, failure modes, quote escaping through the bash -c '...' wrapper, expect-list JSON survival through env var round-trip, builder integration, custom step name / dependsOn
  • Typecheck clean against the new file
  • Exercise end-to-end in sage's next workflow (wire the helper into applySageRepoSetup once this SDK version publishes)

Follow-ups (not this PR)

  1. Update the writing-agent-relay-workflows skill rule with a new Rule 10: "workflows consuming a sibling-repo package MUST declare it via applySiblingLinks"
  2. Retrofit applySageRepoSetup + applyAgentAssistantRepoSetup to use this
  3. Add dispatch branches for Go / Cargo / Gradle as demand appears

Open in Devin Review

Adds a generic, language-agnostic helper that workflow authors can
chain into their setup to link sibling-repo packages into the
workflow's working directory. Solves the "agents fabricate interfaces
when the installed package is stale" class of bugs by making the
real, head-of-main interface visible during the workflow run.

Usage:

  import { workflow, applySiblingLinks } from '@agent-relay/sdk/workflows';

  const wf = applySiblingLinks(workflow('my-feature').pattern('dag'), {
    dependsOn: ['install-deps'],
    links: [
      {
        name: '@agent-assistant/proactive',
        path: '../agent-assistant/packages/proactive',
        expect: ['recordSignal', 'drainSignals'],
      },
    ],
  });

Detection auto-dispatches per manifest:
  - package.json -> 'npm link' (symlinks, no committed manifest change)
  - pyproject.toml / setup.py / setup.cfg -> 'pip install -e' or 'uv pip install -e'

Post-link, when \`expect\` is provided, smoke-tests the linked package
via \`node --input-type=module -e\` (npm) or \`python3 -c\` (python). Any
missing named export fails the step.

MVP scope:
  - npm + python (pip / uv). Auto-detect.
  - Fail-fast on missing sibling path, unknown manifest, or missing
    expected export.
  - Other languages (Go, Cargo, Gradle, Bundler, .NET) land as
    follow-up dispatch branches behind the same public API.

12 tests covering script generation, manifest branches, quote escaping,
expect-list JSON round-trip, and the builder integration.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aad13197ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +180 to +181
' if command -v uv >/dev/null 2>&1; then',
' uv pip install -e "$SIBLING_PATH" --quiet',
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Fall back to pip when uv cannot install outside a venv

The Python branch always prefers uv when it is present, but uv pip install -e fails in environments without an active virtualenv unless --system is provided; I verified this behavior with uv pip install pip --dry-run (No virtual environment found ... pass --system). Because this code runs under set -e, that failure exits immediately and never reaches the pip/pip3 fallback, so sibling-link setup fails on hosts that have uv installed globally but rely on plain pip workflows.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 8f17750. Added --system to the uv invocation AND wrapped it in if command -v uv && uv pip install --system ...; then : so a uv failure (in any environment) falls through to pip/pip3 via the elif chain rather than exiting under set -e. New test case covers the wrapping pattern.

devin-ai-integration[bot]

This comment was marked as resolved.

- Python verify: backslashes inside f-string expressions are a
  SyntaxError on Python < 3.12. Bind `sep = ","` outside the f-string
  and reference it from inside (Devin, 🔴 critical — the verifier
  failed every run previously).

- uv dispatch: uv refuses to install outside a venv without --system.
  Pass --system explicitly AND wrap the uv attempt in an `if ...; then
  :` so failure falls through to pip/pip3 via the elif chain rather
  than exiting under `set -e` (Codex, P1 — broken on hosts with uv
  installed globally but no active venv).

- Echo interpolation: the initial script echoed `link.name` / `link.path`
  into a double-quoted shell string via raw template interpolation, so
  a name containing `\"`, `$`, or backticks would break quoting or
  trigger command substitution. Move the echo after the SIBLING_NAME /
  SIBLING_PATH assignments (which use JSON-encoded safe literals) and
  reference the shell vars (Devin, 🟡).

3 new tests covering each fix; existing test updated for the --system
flag. 15/15 green.
devin-ai-integration[bot]

This comment was marked as resolved.

…stitution

Follow-up to the earlier shell-injection fix on #776. Moving the echo
after the variable assignments was necessary but not sufficient — the
assignments themselves used JSON.stringify (shJsonString) which produces
DOUBLE-quoted bash literals. Double-quoted strings in bash still expand
\$VAR, \$(cmd), and \`cmd\`, so a value like \`../path\$(evil)\` still
triggered command substitution on assignment.

Switch all three assignment sites (SIBLING_PATH, SIBLING_NAME, EXPECT) to
single-quoted bash literals via shSingleQuote, which are literal end-to-
end. Embedded single quotes are handled by the existing '\\'' POSIX
idiom. The EXPECT JSON payload passes through bash untouched and
downstream Node/Python JSON.parse it back.

Removes the now-unused shJsonString helper. Updates two tests to assert
the single-quoted form; adds one more test covering the embedded-single-
quote escape path.

16/16 tests green.
khaliqgant pushed a commit that referenced this pull request Apr 24, 2026
…tion

Nav changes:
  - 'Basics' becomes 'Primitives' and groups related pages semantically:
    * Message (expandable): Channels, DMs, Threads, Emoji reactions
    * File (was 'File sharing')
    * Auth (expandable): Permissions
    * Schedule (was 'Scheduling')
  - 'Spawning an agent' + 'Event handlers' move up into Getting Started
  - Workflows promoted from a single bullet under Advanced to a top-level
    section with 7 pages: Introduction, Quickstart, Builder API,
    Patterns, Setup helpers, Common mistakes, Run from CLI
  - CLI's 'Run workflows' folds into the Workflows section
  - Advanced now only contains Cloud + Workforce

Nav renderer:
  - NavItem gains optional children: NavItem[] for nested sub-items
  - DocsNav.tsx renders children as an indented sub-list with left border
  - getAllDocSlugs() walks the tree for static-generation coverage

New pages (stubbed with real content, not placeholders):
  - workflows-introduction.mdx: landing, mental model, when to use
  - workflows-quickstart.mdx: end-to-end 5-min example
  - workflows-patterns.mdx: 80-to-100, lead+workers, multi-file edit,
    cross-repo, supervisor, parallelism/waves
  - workflows-setup-helpers.mdx: per-repo setup helper convention +
    applySiblingLinks (landing in relay SDK #776)
  - workflows-common-mistakes.mdx: the table every workflow author
    needs once, ported from the writing-agent-relay-workflows skill

Content porting is from the two private Claude-Code skills
(writing-agent-relay-workflows and relay-80-100-workflow) so this
content is now public + addressable by URL.

DRAFT PR. Open questions for review:
  - Should 'Permissions' be under Auth or its own primitive?
  - 'Sending messages' acts as the Message landing — separate page or
    umbrella?
  - Should there be an 'Agent' primitive (for Spawning + Event handlers)
    rather than putting them in Getting Started?
@khaliqgant khaliqgant merged commit 2985b5d into main Apr 24, 2026
40 checks passed
@khaliqgant khaliqgant deleted the feat/sdk-sibling-links branch April 24, 2026 07:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant