Skip to content

feat(harness): add forge and cursor to the canonical harness enum - #105

Merged
drewstone merged 2 commits into
mainfrom
feat/harness-forge-cursor
Jul 31, 2026
Merged

feat(harness): add forge and cursor to the canonical harness enum#105
drewstone merged 2 commits into
mainfrom
feat/harness-forge-cursor

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Problem

Four enums describe "which coding agent runs this profile", and they disagree:

definition repo count
HarnessType / harnessTypeSchema (canonical, has a drift guard) agent-sdk packages/agent-interface/src/harness.ts 13 → 15
BACKEND_TYPE / backendTypeSchema agent-dev-container packages/runtime-contracts/src/backend-types.ts 14
KNOWN_HARNESSES / Harness agent-app src/harness/index.ts 14
MATERIALIZER_HARNESSES / HarnessId agent-dev-container packages/agent-profile-materialize/src/index.ts 9

forge and cursor ship full provider adapters downstream (packages/sdk-provider-forge, packages/sdk-provider-cursor — both with a readonly provider = "…" adapter class) but were absent from the canonical enum. That absence is what forced the other two packages to keep divergent copies, and it made agent-app's documented claim that its taxonomy is "a superset of agent-interface's HarnessType" unenforceable — three as HarnessType casts rest on it.

Change

Add forge and cursor to HarnessType and harnessTypeSchema. The existing compile-time drift guard keeps the TS union and the Zod enum in lockstep.

Both are multi-provider CLI runners with no vendor lock, so they need no entry in the capability tables — harnessProviderLock, harnessPreferredModelPatterns, harnessReasoningEffortsOverride, harnessReasoningCeiling, harnessIgnoresModel, harnessIgnoresEffort are all Partial<Record<HarnessType, …>> and default to router-backed / full ladder / honors-both-selectors. That is exactly the behavior agent-app already documented and relied on, so this is additive with no behavior change for existing ids.

harness.test.ts pins harnessTypeSchema.options, so downstream packages can derive their enums from it and an accidental add/remove fails here rather than silently three repos away.

Verification

Full gate on a clean worktree off origin/main, before and after:

check-types  exit 0
build        exit 0
test         exit 0  — agent-interface 23 files / 245 tests (was 22 / 241)
                       agent-core 17 / 419, cli-bridge 2 / 58, all providers pass

Baseline note: pnpm test without a prior pnpm build fails 3 agent-core test files with Failed to resolve entry for package "@tangle-network/agent-interface" — an unbuilt-dist artifact, not a defect. Building first is green both before and after this change.

Follow-ups (separate PRs, not merged here)

  • agent-dev-container: derive BACKEND_TYPE from harnessTypeSchema.options, add satisfies readonly HarnessType[] to MATERIALIZER_HARNESSES, drop the batch-request enum intersection.
  • agent-app: derive KNOWN_HARNESSES from the canonical options and fix the false superset claim.

Those consume the published enum, so they land against the currently-published agent-interface and pick forge/cursor up from an explicit named extras list until this ships.

Both ship full provider adapters downstream (sdk-provider-forge,
sdk-provider-cursor) but were absent from HarnessType, which forced
agent-dev-container and agent-app to keep divergent copies of the harness
taxonomy and made agent-app's documented "superset of HarnessType" claim
unenforceable.

They are multi-provider CLI runners with no vendor lock, so they need no entry
in the capability tables and resolve as router-backed. harness.test.ts pins
harnessTypeSchema.options so downstream packages can derive from it.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Auto-approved drewstone PR — 802d0946

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-31T17:28:22Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 145.4s (2 bridge agents)
Total 145.4s

💰 Value — sound

Adds forge and cursor to the canonical HarnessType enum — two missing multi-provider CLI harnesses whose provider adapters already exist downstream. Minimal, in-grain, tight.

  • What it does: Adds forge and cursor as string literal members to the HarnessType union type and the harnessTypeSchema Zod enum in packages/agent-interface/src/harness.ts (the canonical harness definition). Also adds a new harness.test.ts that pins the full set of 15 canonical harness IDs and tests they validate, plus a capability test confirming both are router-backed with no vendor lock.
  • Goals it achieves: Eliminates the divergence between the canonical harness enum (in this repo) and the downstream provider adapters that already ship for forge and cursor (in other repos). Once merged, agent-dev-container's BACKEND_TYPE and agent-app's KNOWN_HARNESSES can derive from this single source of truth instead of maintaining divergent copies. The compile-time MutuallyAssignable drift guard keeps the T
  • Assessment: Sound, minimal change that follows the codebase's existing patterns exactly. Both forge and cursor are multi-provider CLI runners with no vendor lock, so they need no entries in any of the Partial<Record<HarnessType, ...>> capability tables — they correctly fall through to router-backed defaults (harness-capabilities.ts:64-66). The new test file mirrors the same validation approach the codebase
  • Better / existing approach: none — this is the right approach. The canonical enum is the authoritative list by design. Adding two IDs to it is the only coherent action when downstream packages already ship adapters for those IDs. No existing mechanism in this repo (grep'd for BACKEND_TYPE, KNOWN_HARNESSES) auto-derives or consolidates harness values — the canonical enum is the mechanism.
  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge warning: opencode/kimi-for-coding/k2p7: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":"queue_timeout","admission":{"active":20,"queued":0,"maxActive":20,"maxQueue":48}}}; opencode/zai-coding-plan/glm-5.2: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":

🎯 Usefulness — error

usefulness agent produced no parseable value-audit JSON.

  • Model: opencode/deepseek/deepseek-v4-pro
  • Bridge attempts: 3
  • Bridge error: opencode/zai-coding-plan/glm-5.2: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":"queue_timeout","admission":{"active":20,"queued":1,"maxActive":20,"maxQueue":48}}}; opencode/kimi-for-coding/k2p7: Bridge returned 503: {"error":{"message":"cli-bridge admission timed out after 30000ms","type":"admission_rejected","reason":

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260731T173604Z

@tangletools

Copy link
Copy Markdown

✅ No Blockers — 802d0946

Review health 100/100 · Reviewer score 89/100 · Confidence 70/100 · 5 findings (5 low)

glm deepseek deepseek-flash aggregate
Readiness 89 92 89 89
Confidence 70 70 70 70
Correctness 89 92 89 89
Security 89 92 89 89
Testing 89 92 89 89
Architecture 89 92 89 89

Reviewer score is advisory once the run is complete and the verdict has no blockers.

Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision.

🟡 LOW No explicit reasoning-effort test for new router-backed harnesses — packages/agent-interface/src/harness-capabilities.test.ts

The test verifies providers, model support, snapping, and selectors for forge/cursor, but not harnessReasoningEfforts. Since forge/cursor have no entry in harnessReasoningEffortsOverride or harnessReasoningCeiling, they default to the full reasoningLadder (harness-capabilities.ts:193). This is correct — same behavior as opencode — but no test proves it. Add expect(harnessReasoningEfforts('forge')).toEqual(reasoningLadder) to lock in the contract and prevent a future capability-table entry from silently changing the output.

🟡 LOW forge/cursor selector+effort capabilities are un-audited defaults, not adapter facts — packages/agent-interface/src/harness-capabilities.test.ts

The new test pins harnessHonorsSelectors('forge')/('cursor') === true and (via no override/ceiling entry) the full reasoning ladder. But in harness-capabilities.ts these are DEFAULT-BY-ABSENCE behaviors: harnessIgnoresModel/harnessIgnoresEffort/harnessReasoningEffortsOverride/harnessReasoningCeiling classify every other harness from a documented 'cli-bridge adapter audit' (see comment at harness-capabilities.ts:227-237), while forge/cursor get no entry and silently inherit 'honors everything'. The changeset asserts downstream already relies on router-backed behavior, but nothing in this repo validates that the real sdk-provider-forge / sdk-provider-cursor adapters honor per-turn model/effort selectors — if they drop them (as many CLI runners do), the picker would silently ignore the user's

🟡 LOW forge/cursor selector-honoring is default-inherited, not adapter-verified — packages/agent-interface/src/harness-capabilities.test.ts

The new test asserts harnessHonorsSelectors('forge'|'cursor') is true, and it passes — but only because both harnesses are absent from harnessIgnoresModel/harnessIgnoresEffort in harness-capabilities.ts:239,250. Every other entry in those sets is documented as 'grounded in the cli-bridge adapter audit, NOT a guess' (harness-capabilities.ts:227-228). forge (tailcallhq/forgecode) and cursor (cursor-agent) get the honor-by-default path with no adapter evidence cited in this repo. The test therefore confirms the table default, not the real CLI behavior — it would stay green even if a downstream adapter actually dropped the per-turn model/effort. Impact is low: both are explicitly designated router-backed (caller's selections are respected by construction), so the claim is defensible; b

🟡 LOW Third harness.test.ts test is redundant with the exact-set pin — packages/agent-interface/src/harness.test.ts

The 'carries the CLI runners...' test (lines 43-49) asserts only that harnessTypeSchema.options contains 'forge' and 'cursor' — both already enforced by the first test's exact-set equality (CANONICAL_HARNESSES includes forge/cursor at lines 23-24). It adds no failing signal beyond the pin and doubles the maintenance surface when the list next changes. Either fold the rationale into the CANONICAL_HARNESSES comment and delete the third test, or keep it only as documented history. Harmless; does not affect

🟡 LOW harness.test.ts downstream-derivation claim is not enforceable from this repo — packages/agent-interface/src/harness.test.ts

The header comment states this list is what 'agent-dev-container's BACKEND_TYPE and agent-app's KNOWN_HARNESSES' derive from. Those packages live in other repos and are not checked here, so the comment is a statement of intent rather than something the test guards. The test itself only pins the in-package canonical set against harnessTypeSchema.options — which is genuinely useful as a regression guard against accidental add/remove within this repo. No functional defect; just flagging that a future reader shouldn't read the comment as evidence of cross-repo coverage. Optional: soften wording to 'is intended to be the canonical set downstream packages derive from' or add a TODO linking the downstream sync check.


tangletools · 2026-07-31T17:37:45Z · trace

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved — 5 non-blocking findings — 802d0946

Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision. | Full multi-shot audit completed 2/2 planned shots over 4 changed files. Global verifier still owns final merge decision.

Full immutable report for this review: trace

Summary comment for this run: full summary


tangletools · 2026-07-31T17:37:45Z · immutable trace

The separate forge/cursor membership test asserted only what the exact-set pin
already enforces, so it added no failing signal and doubled the maintenance
surface when the list next changes.
@drewstone
drewstone merged commit 32acb32 into main Jul 31, 2026
1 check passed
@drewstone
drewstone deleted the feat/harness-forge-cursor branch July 31, 2026 18:06
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.

2 participants