Skip to content

feat(interface): preserve exact recursive agent profiles - #104

Open
drewstone wants to merge 4 commits into
mainfrom
fix/cli-bridge-exact-profile
Open

feat(interface): preserve exact recursive agent profiles#104
drewstone wants to merge 4 commits into
mainfrom
fix/cli-bridge-exact-profile

Conversation

@drewstone

@drewstone drewstone commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

What changed

  • send tasks as tasks without duplicating profile instructions into messages
  • forward the exact AgentProfile and reasoning effort to CLI providers
  • expose provider sessions as durable, resumable handles
  • export agentProfileJsonSchema from the canonical Zod input contract so model-authored child profiles receive the real shape

Why

Recursive agents must author complete child profiles, and provider adapters must preserve those profiles and sessions without inventing policy. The old coordination tool described profile only as an object, so models invented invalid fields.

Verification

  • agent-interface: 750/750 tests passed
  • all 8 SDK builds passed
  • package-artifact check: 36/36 targets passed
  • canonical JSON Schema round-trips a complete AgentProfile through z.fromJSONSchema and the canonical validator

@drewstone drewstone changed the title fix(cli-bridge): preserve exact profile authority feat(interface): preserve exact recursive agent profiles Jul 30, 2026
@drewstone
drewstone marked this pull request as ready for review July 30, 2026 19:21

@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 — dcfa8ee8

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-30T19:28:15Z

@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-with-nits

Verdict sound-with-nits
Concerns 1 (1 weak-concern)
Heuristic 0.0s
Duplication 0.0s
Interrogation 140.1s (2 bridge agents)
Total 140.1s

💰 Value — sound-with-nits

Makes the cli-bridge forward profiles/sessions intact and exports a canonical profile JSON Schema — all coherent and in-grain, with one minor near-duplicate helper.

  • What it does: Four coupled changes: (1) cli-bridge now requires an inline AgentProfile, freezes it via the existing snapshotAgentProfile, and forwards it unchanged in the agent_profile field plus reasoning effort, instead of copying the system prompt into a system message; (2) cli-bridge implements dispatch()/session() returning durable, resumable AgentSession handles (detach + cursor replay); (3) exports agent
  • Goals it achieves: Recursive agents that author child profiles get the real, validated profile shape handed to providers verbatim — no field invention (the JSON Schema), no authority duplication into messages (single source = the profile), and durable/resumable sessions so a parent can dispatch and reattach. It also fixes a latent doc/code contradiction: the docs claimed harness was not part of identity while canoni
  • Assessment: Sound and in the grain. The cli-bridge dispatch/session work catches up to an interface (environment-provider.ts:273-292) that agent-provider-tangle already implements — extending, not inventing. agentProfileJsonSchema is generated from the single canonical validator rather than hand-maintained (avoids drift), and stripping the ^u(?:[0-9a-f]{4})*$ propertyNames is correct because that pattern is t
  • Better / existing approach: Searched for an existing JSON-schema emitter (only z.toJSONSchema here, no prior art), an existing coordination tool consuming this schema (none in-repo — it's a published library export awaiting consumers, acceptable for an SDK package), and existing session-handle implementations. None should be reused over the chosen design. One near-duplicate helper exists (see findings).
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content

🎯 Usefulness — sound

A coherent change that conforms to existing interface contracts (durable sessions already implemented by agent-provider-tangle), preserves exact profile authority at intake via an existing snapshot primitive, and exposes the canonical profile JSON Schema derived from the validator rather than duplic

  • Integration: All new behavior wires into established, reachable surface. The cli-bridge's dispatch()/session() implement the optional methods already declared on AgentEnvironment (environment-provider.ts:291-292), already implemented by agent-provider-tangle (index.ts:227,238), and already validated by agent-provider-testkit (index.ts:104-111). snapshotAgentProfile is an existing primitive now consumed at the
  • Fit with existing patterns: Fits the codebase grain precisely. The durable-session implementation mirrors agent-provider-tangle's shape (polymorphism, not duplication). The JSON Schema is generated from the canonical Zod validator with narrowly-targeted removal of Zod's internal record-key encoding artifacts (the ^u(?:[0-9a-f]{4})*$ pattern from ownPropertyRecordSchema) and the root $schema, rather than hand-maintaining a se
  • Real-world viability: Tests cover well beyond the happy path: profile immutability across intake (caller mutation of prompt/model rejected), named-profile rejection before any network use, exact forwarding of agent_profile + reasoning effort, durable dispatch with verified HTTP reader detachment, cursor-based replay, usage-preserving terminal results, session continuation, terminal-confirmed cancellation, request-diges
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

💰 Value Audit

🟡 Local addTokenUsage near-duplicates agent-core's shared helper [duplication] ``

cli-bridge adds its own addTokenUsage (index.ts:666-704) that accumulates the full TokenUsage shape (inputTokens, outputTokens, plus totalTokens, cacheRead/Creation, reasoningTokens, cost). A shared addTokenUsage already exists at packages/agent-core/src/telemetry/token-usage.ts:135 and is exported from agent-core — but it only sums inputTokens+outputTokens, so it is too thin to reuse here as-is. Two options for a reviewer to weigh: extend the shared helper to accumulate the full TokenUsageCount


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 · 20260730T193052Z

@tangletools

Copy link
Copy Markdown

✅ No Blockers — dcfa8ee8

Review health 100/100 · Reviewer score 71/100 · Confidence 95/100 · 8 findings (8 low)

glm: Correctness 71 · Security 71 · Testing 71 · Architecture 71

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

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

🟡 LOW Redundant patch under a higher-level minor changeset for the same package — .changeset/agent-profile-routing-identity.md

@tangle-network/agent-interface is also bumped minor by .changeset/agent-profile-json-schema.md in this same PR. Changesets accumulate and the highest level wins, so this patch is subsumed at changeset version time. Harmless and by-design; noted only for changeset hygiene. Optional fix: fold this summary into the minor changeset or drop it.

🟡 LOW authoredProfileDigest/effectiveProfileDigest docstrings describe receipt design that predates this PR — packages/agent-interface/src/agent-execution-preparation.ts

The added docstrings 'Canonical identity before executor overrides' and 'Canonical identity after executor overrides' correctly describe the fields, but these fields were already present (the diff only adds comments). The semantic alignment between the harness.ts/agent-profile.ts docstring changes and these receipt fields is the real value — the PR retroactively documents that harness was always part of canonicalAgentProfileDigest's output when present. No issue; just noting the behavioral change is docs-only, not code-level.

🟡 LOW Model-facing JSON Schema diverges from canonical validator for hostile own keys — packages/agent-interface/src/profile-schema.ts

The canonical agentProfileSchema uses ownPropertyRecordSchema (lines 30-56) which preprocesses keys via Object.entries + encodeRecordKey, preserving hostile own keys like proto as data properties. The new agentProfileJsonSchema, when round-tripped through z.fromJSONSchema (as the test at profile-schema.test.ts:540 does), produces a validator that silently drops proto keys — confirmed at runtime: z.fromJSONSchema(agentProfileJsonSchema).safeParse(JSON.parse('{"tools":{"proto":true,"normal":true}}')) yields tools keys ['normal'] only, while agentProfileSchema yields ['proto','normal']. The docstring ([line 450](https://github.com/

🟡 LOW removeModelInputSchemaArtifacts is implicitly coupled to Zod's propertyNames emission choice — packages/agent-interface/src/profile-schema.ts

The function strips propertyNames entries whose value matches the encoded record-key pattern (isEncodedRecordKeyPropertyNames, line 409). Confirmed at runtime: the raw Zod output has exactly 25 propertyNames entries, all matching this pattern, and zero patternProperties entries — so the function is correct today. However, if a future Zod version changes to emit record-key constraints via patternProperties or inline pattern fields instead of propertyNames, this function would silently fail to strip the encoded-key artifacts from the model-facing schema. The existing test (profile-schema.test.ts:425, asserting serialized does not contain the encoded

🟡 LOW Replay-retention failure mode is documented but not asserted by a client-side test — packages/agent-provider-cli-bridge/README.md

The README states 'Replay and result reads fail loudly after cli-bridge's configured replay retention expires.' This is server-enforced behavior. The client src/index.ts surfaces HTTP status failures (replay result throws on non-2xx, L1031/1039) but no test in src/index.test.ts exercises the retention-expired path, so the documented contract is only validated against the live server, not in CI. Low impact: docs accuracy for an out-of-process guarantee. Optional: add a mocked-replay-expired test to lock the client's error shape.

🟡 LOW Concurrent session.prompt silently orphans prior run from session.current — packages/agent-provider-cli-bridge/src/index.ts

bindCliBridgeSession (line 258-266) unconditionally replaces sessions[sessionId].current with the new run, returning the previous run. On the success path of streamTrackedCliBridgeTurn, restoreCliBridgeSession is NOT called (only on rejection/error at lines 330-331, 429). So if prompt-A is still streaming when prompt-B succeeds, session.current permanently points to B even after A completes. The test 'keeps concurrent continuation results bound to their own run identity' (index.test.ts:376) pr

🟡 LOW collectCliBridgeTurnResult can mask stream errors with GET status errors — packages/agent-provider-cli-bridge/src/index.ts

If the event stream errors (streamError captured at line 622-624), the function then calls await getCliBridgeRun(options, transport, run.id) (line 626). If that GET also fails (network error, 500, etc.), the GET error propagates and the original streamError is silently discarded — the caller never sees the root-cause stream failure. Impact: low — debugging only; the caller still knows the turn failed. Fix: wrap getCliBridgeRun in try/catch and prefer streamError if the GET throws: `let snapshot;

🟡 LOW readFullCliBridgeResult omits x-session-id header that the streaming path sends — packages/agent-provider-cli-bridge/src/index.ts

The streaming POST in streamCliBridgeTurn (line 859) sends ...(turn.sessionId ? { "x-session-id": turn.sessionId } : {}), but readFullCliBridgeResult (line 1024-1029) sends only headers: requestHeaders(options) — no x-session-id. This PR adds onAccepted to readFullCliBridgeResult (line 1033), which calls captureCliBridgeRunIdentity, now requiring


tangletools · 2026-07-30T19:42:00Z · 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 — 8 non-blocking findings — dcfa8ee8

Full multi-shot audit completed 8/8 planned shots over 15 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-30T19:42:00Z · immutable trace

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