Category: spec-conformance Severity: minor
Location: Sources/ARCP/Runtime/JobManager.swift:658-680
Spec: ARCP v1.1 §8.4
What
§8.4: implementations MUST NOT mix inline result and result_chunk in one job, and once any chunk is emitted the terminating job.result MUST carry result_id. The mixing guard only covers .value; a handler that emits chunks then returns .ref or .empty produces a terminal job.completed with an inline ref / no result_id, violating both rules.
Evidence
private func toCompleted(jobId: JobId, _ output: ToolOutput) throws -> JobCompletedPayload {
let streamedResultId = jobs[jobId]?.streamedResultId
switch output {
case .value(let value):
if let streamedResultId { throw ARCPError.invalidArgument(...) }
return JobCompletedPayload(result: value)
case .ref(let ref): return JobCompletedPayload(resultRef: ref)
case .empty: return JobCompletedPayload()
...
Proposed fix
When streamedResultId is set, reject .ref (mixing) and treat .empty as a streamed completion that must carry the streamed result_id rather than an empty payload.
Acceptance criteria
Category: spec-conformance Severity: minor
Location:
Sources/ARCP/Runtime/JobManager.swift:658-680Spec: ARCP v1.1 §8.4
What
§8.4: implementations MUST NOT mix inline result and result_chunk in one job, and once any chunk is emitted the terminating job.result MUST carry result_id. The mixing guard only covers
.value; a handler that emits chunks then returns.refor.emptyproduces a terminal job.completed with an inline ref / no result_id, violating both rules.Evidence
Proposed fix
When streamedResultId is set, reject
.ref(mixing) and treat.emptyas a streamed completion that must carry the streamed result_id rather than an empty payload.Acceptance criteria