ARCP v1.1 §8.4 introduces result_chunk event for streamed results. Not in PHP SDK.
Steps:
- Add
ResultChunk message in src/Messages/Job/
- Register
@SerialName('result_chunk') (or PHP equivalent attribute)
- Implement client-side stream assembler concatenating chunks until
final: true
- Add
samples/result_chunk/ demo
- Tests
Reference: spec §8.4.
Audit reference: ARCP SDK Audit v1.1 (2026-05-19)
Implementation prompt
Largely landed; this is a "verify + harden" pass. PSR-4 root: Arcp\ -> src/. Tests: Arcp\Tests\ -> tests/. Spec ref: spec/docs/draft-arcp-1.1.md §8.4. Note: in this codebase the message lives under src/Messages/Execution/, not src/Messages/Job/.
Files to touch
src/Messages/Execution/ResultChunk.php — existing. Verify only.
src/Envelope/MessageCatalog.php — confirm ResultChunk::class in CORE_CLASSES.
src/Client/ResultChunkAssembler.php — existing assembler. Verify only.
src/Client/ARCPClient.php — confirm public readonly ResultChunkAssembler $resultChunks and that runReadLoop() pushes ResultChunk payloads into it before routing.
src/Runtime/JobContext.php — confirm emitResultChunk(string $resultId, string $data, bool $more = true, string $encoding = 'utf8'): void increments $job->nextResultChunkSeq($resultId).
src/Runtime/Job.php — confirm nextResultChunkSeq() is per-result-id.
samples/result_chunk/main.php — existing. Verify runnable.
tests/Unit/Client/ResultChunkAssemblerTest.php (new) — unit.
tests/Integration/ResultChunkTest.php (new) — integration.
Public API additions
Namespace Arcp\Messages\Execution:
final readonly class ResultChunk extends MessageType {
public function __construct(
public string $resultId,
public int $chunkSeq, // monotonically increasing per resultId, starts at 0
public string $data,
public string $encoding = 'utf8', // 'utf8' | 'base64'
public bool $more = true, // false on the final chunk
);
}
Namespace Arcp\Client:
final class ResultChunkAssembler {
public function push(ResultChunk $chunk): void;
public function isComplete(string $resultId): bool;
public function assemble(string $resultId): string; // throws InvalidArgumentException if unknown
}
On JobContext:
public function emitResultChunk(
string $resultId,
string $data,
bool $more = true,
string $encoding = 'utf8',
): void;
Step-by-step changes
src/Messages/Execution/ResultChunk.php: confirm typeName() returns 'job.result_chunk'. Confirm fromArray() validates encoding in {utf8, base64} and rejects negative chunk_seq. No edits expected.
src/Envelope/MessageCatalog.php: confirm ResultChunk::class is in the Execution block of CORE_CLASSES.
src/Client/ResultChunkAssembler.php: confirm assemble() sorts chunks by chunkSeq, decodes base64 chunks via base64_decode(..., strict: true), and rejects malformed base64 with InvalidArgumentException. If chunks arrive out of order, ksort() must put them back in order before concatenation.
src/Client/ARCPClient.php runReadLoop(): confirm if ($env->payload instanceof ResultChunk) { $this->resultChunks->push($env->payload); } runs before $this->router->handle($env). Spec §8.4 requires the assembler to see every chunk even when the application also subscribes via the router.
src/Runtime/Job.php: confirm nextResultChunkSeq(string $resultId): int returns a fresh counter per resultId (0, 1, 2, ...).
src/Runtime/JobContext.php emitResultChunk(): confirm it pulls the Job via $this->runtime->jobs->tryGet($this->jobId), calls $job->nextResultChunkSeq($resultId), and emits the ResultChunk envelope with job_id + trace_id envelope hints.
samples/result_chunk/main.php: open, confirm it streams (e.g.) 5 chunks then a final chunk with more=false, and the sample client calls $assembler->assemble($resultId) after receiving the final chunk. Run php samples/result_chunk/main.php.
tests/Unit/Client/ResultChunkAssemblerTest.php (new):
testAssemblesUtf8ChunksInOrder() — push chunks 0..3, last with more=false, assert assemble() returns concatenation.
testAssemblesOutOfOrderChunks() — push in random order (seq 2, 0, 3, 1), assert correct concatenation.
testBase64ChunksDecoded() — push base64-encoded chunks, assert binary output.
testRejectsMalformedBase64() — push invalid base64 then call assemble(), expect InvalidArgumentException.
testIsCompleteOnlyAfterFinal() — assert isComplete() is false until a chunk with more=false arrives.
testAssembleUnknownResultIdThrows().
tests/Integration/ResultChunkTest.php (new):
- Register a tool that emits 4 chunks via
$ctx->emitResultChunk(...) then returns. Capture client side: after invokeTool resolves, assert $client->resultChunks->isComplete($rid) and $client->resultChunks->assemble($rid) equals expected joined payload.
- Add a test that interleaves two
resultIds in one job — assert each id's chunks assemble independently.
Tests to add
tests/Unit/Client/ResultChunkAssemblerTest.php (cases above).
tests/Integration/ResultChunkTest.php:
testStreamingResultAssembles()
testInterleavedResultIdsAssembleIndependently()
- Add a round-trip case in
tests/Unit/MessageCatalogRoundTripTest.php if not already covered.
Verification commands
cd /Users/nficano/code/arpc/php-sdk
composer test -- --filter ResultChunk
composer stan
composer psalm
php samples/result_chunk/main.php
Acceptance
- [task]
ResultChunk message registered in catalog with type job.result_chunk.
- [task]
chunk_seq monotonic per result_id (Job-level counter).
- [task]
encoding validated to utf8 or base64 at deserialization.
- [task] Client read-loop unconditionally pushes
ResultChunk into ResultChunkAssembler.
- [task]
ResultChunkAssembler::assemble() produces correct bytes even when chunks arrive out of order.
- [task] Base64 chunks decode strictly; malformed payload throws
InvalidArgumentException.
- [task]
samples/result_chunk/main.php runs to completion.
- [task] Unit and integration tests added.
- [task]
composer stan && composer psalm && composer test all pass.
ARCP v1.1 §8.4 introduces
result_chunkevent for streamed results. Not in PHP SDK.Steps:
ResultChunkmessage insrc/Messages/Job/@SerialName('result_chunk')(or PHP equivalent attribute)final: truesamples/result_chunk/demoReference: spec §8.4.
Audit reference: ARCP SDK Audit v1.1 (2026-05-19)
Implementation prompt
Largely landed; this is a "verify + harden" pass. PSR-4 root:
Arcp\->src/. Tests:Arcp\Tests\->tests/. Spec ref:spec/docs/draft-arcp-1.1.md§8.4. Note: in this codebase the message lives undersrc/Messages/Execution/, notsrc/Messages/Job/.Files to touch
src/Messages/Execution/ResultChunk.php— existing. Verify only.src/Envelope/MessageCatalog.php— confirmResultChunk::classinCORE_CLASSES.src/Client/ResultChunkAssembler.php— existing assembler. Verify only.src/Client/ARCPClient.php— confirmpublic readonly ResultChunkAssembler $resultChunksand thatrunReadLoop()pushesResultChunkpayloads into it before routing.src/Runtime/JobContext.php— confirmemitResultChunk(string $resultId, string $data, bool $more = true, string $encoding = 'utf8'): voidincrements$job->nextResultChunkSeq($resultId).src/Runtime/Job.php— confirmnextResultChunkSeq()is per-result-id.samples/result_chunk/main.php— existing. Verify runnable.tests/Unit/Client/ResultChunkAssemblerTest.php(new)— unit.tests/Integration/ResultChunkTest.php(new)— integration.Public API additions
Namespace
Arcp\Messages\Execution:Namespace
Arcp\Client:On
JobContext:Step-by-step changes
src/Messages/Execution/ResultChunk.php: confirmtypeName()returns'job.result_chunk'. ConfirmfromArray()validatesencoding in {utf8, base64}and rejects negativechunk_seq. No edits expected.src/Envelope/MessageCatalog.php: confirmResultChunk::classis in the Execution block ofCORE_CLASSES.src/Client/ResultChunkAssembler.php: confirmassemble()sorts chunks bychunkSeq, decodes base64 chunks viabase64_decode(..., strict: true), and rejects malformed base64 withInvalidArgumentException. If chunks arrive out of order,ksort()must put them back in order before concatenation.src/Client/ARCPClient.phprunReadLoop(): confirmif ($env->payload instanceof ResultChunk) { $this->resultChunks->push($env->payload); }runs before$this->router->handle($env). Spec §8.4 requires the assembler to see every chunk even when the application also subscribes via the router.src/Runtime/Job.php: confirmnextResultChunkSeq(string $resultId): intreturns a fresh counter perresultId(0, 1, 2, ...).src/Runtime/JobContext.phpemitResultChunk(): confirm it pulls theJobvia$this->runtime->jobs->tryGet($this->jobId), calls$job->nextResultChunkSeq($resultId), and emits theResultChunkenvelope withjob_id+trace_idenvelope hints.samples/result_chunk/main.php: open, confirm it streams (e.g.) 5 chunks then a final chunk withmore=false, and the sample client calls$assembler->assemble($resultId)after receiving the final chunk. Runphp samples/result_chunk/main.php.tests/Unit/Client/ResultChunkAssemblerTest.php(new):testAssemblesUtf8ChunksInOrder()— push chunks 0..3, last withmore=false, assertassemble()returns concatenation.testAssemblesOutOfOrderChunks()— push in random order (seq 2, 0, 3, 1), assert correct concatenation.testBase64ChunksDecoded()— push base64-encoded chunks, assert binary output.testRejectsMalformedBase64()— push invalid base64 then callassemble(), expectInvalidArgumentException.testIsCompleteOnlyAfterFinal()— assertisComplete()is false until a chunk withmore=falsearrives.testAssembleUnknownResultIdThrows().tests/Integration/ResultChunkTest.php(new):$ctx->emitResultChunk(...)then returns. Capture client side: afterinvokeToolresolves, assert$client->resultChunks->isComplete($rid)and$client->resultChunks->assemble($rid)equals expected joined payload.resultIds in one job — assert each id's chunks assemble independently.Tests to add
tests/Unit/Client/ResultChunkAssemblerTest.php(cases above).tests/Integration/ResultChunkTest.php:testStreamingResultAssembles()testInterleavedResultIdsAssembleIndependently()tests/Unit/MessageCatalogRoundTripTest.phpif not already covered.Verification commands
Acceptance
ResultChunkmessage registered in catalog with typejob.result_chunk.chunk_seqmonotonic perresult_id(Job-level counter).encodingvalidated toutf8orbase64at deserialization.ResultChunkintoResultChunkAssembler.ResultChunkAssembler::assemble()produces correct bytes even when chunks arrive out of order.InvalidArgumentException.samples/result_chunk/main.phpruns to completion.composer stan && composer psalm && composer testall pass.