feat(mirror): send the tool-call trajectory to Temporal, metadata only #888
No reviewers
Labels
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo!888
Loading…
Reference in a new issue
No description provided.
Delete branch "aos/claude/hs68-temporal-mirror"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Mirroring, not orchestration. Temporal observes the turn and never runs it, so nothing here sits in the control path and nothing compounds with agent-proxy's fallback.
The payload is a struct, and that is the safety property
ToolCallRecordhas five named fields: server, tool, outcome, elapsed millis, trace id.It is deliberately not the span's attribute slice. The issue named the exact failure and it is worth restating, because everything else in this PR is downstream of it:
StartSpanis a variadic passthrough, so copying from it would mirror whatever any caller passed, sight unseen, forever. Someone addsattribute.String("message.text", ...)to a span one day and Discord member content starts flowing to a third-party SaaS, with no change at the mirror and nothing to notice it.Widening what leaves now means adding a field to that struct, which is a disclosure decision someone makes on purpose.
TestTheMirroredPayloadCarriesNothingButMetadataenumerates the fields and fails when the set changes, rather than sampling for known-bad strings.The trace id is the only field outside
RecordToolCall's own triple. It names no person, and a member is already handed one in a failure notice.Tool calls, not spans
Only
RecordToolCallmirrors.TestOnlyToolCallsAreMirroredruns threeStartSpancalls, aRecordTurn, aRecordModelCalland aRecordFailurealongside two tool calls, and asserts two records.SignalWithStartWorkflowkeyed onsirens-deep-trajectory-<trace id>is one action whether the turn's workflow exists yet or not, so volume tracks tool calls. A turn's calls land in one ordered trajectory instead of one workflow each, which is also what makes it a trajectory rather than a pile.Nothing about it can reach a turn
Four ways this could have become a new way for turns to die, and what each does instead:
Each increments
sirens_echo.mirror.drops, so an outage is a number rather than the silence #137 and #190 describe. The delivery context is detached from the turn, so a finished turn does not cancel its own audit record.TestAStalledMirrorNeverBlocksTheTurnsends 500 records with the only worker wedged and asserts the sender returns.Configuration
Off unless a deployment supplies all of
SIRENS_ECHO_TEMPORAL_HOST,_NAMESPACE, and_TASK_QUEUE.A half-filled connection fails at boot. A typo that quietly turned the mirror off would be the same silent failure the drop counter exists to prevent. A dial failure is deliberately different and only logs: Temporal being unreachable must never stop this service answering.
Deep only, and the owl-glass surface first, are deployment choices made by which lane sets the variables. That is
coilyco-bridge/deploy's, per the issue's scope boundary. The credential is #444.Acceptance
TestAToolCallIsMirroredWithItsMetadata,TestTheSignalCarriesTheRecordUnchangedTestTheMirroredPayloadCarriesNothingButMetadata- field enumeration plus an encoded-form checkTestAStalledMirrorNeverBlocksTheTurn,TestAFailingMirrorIsCountedAndNeverReturned,TestAPanickingMirrorIsContainedTestEachToolCallIsOneSignalKeyedOnTheTurn,TestOnlyToolCallsAreMirroredTestAContentBearingSpanAttributeDoesNotReachTheMirror- putsmessage.textandmcp.tool.argumentson the span, asserts neither reaches the payloadTwo things worth a reviewer's eye
go.temporal.io/sdkmoves the go directive from 1.25.0 to 1.25.4, because the SDK's owngo.modrequires it. NoGOTOOLCHAINpin exists in CI, so Go's toolchain switching handles it, and CI is the check.Nothing here has talked to Temporal Cloud. The credential is #444 and the deployment wiring is deploy's, so every test substitutes the one client call behind an interface. The transport is the part still unproven, and the first real connection will be the test of it.
just gatepasses.closes #887
Deep now emits a durable record of what it called into Temporal Cloud. Temporal observes the turn rather than running it, so nothing here is in the control path and nothing compounds with agent-proxy's fallback. The payload is a five-field struct, not the span's attribute slice. StartSpan is a variadic passthrough, so copying from it would mirror whatever any caller passed, sight unseen, forever: someone adds attribute.String("message.text", ..) to a span one day and member content starts flowing to a third-party SaaS with no change at the mirror and nothing to notice it. Widening what leaves now means adding a field, which a test enumerates and fails on. Only RecordToolCall mirrors. StartSpan runs many times per turn and does not, because hooking span-start points a firehose at a service that bills per action. SignalWithStart keyed on the trace id is one action per call and gives a turn one ordered trajectory rather than one workflow per call. Nothing about it can reach a turn. The send is a non-blocking channel write, one worker owns the only call, the context is detached from the turn and hard bounded, errors are swallowed and a panicking client is recovered. Every one of those paths counts sirens_echo.mirror.drops, so an outage is a number rather than the silence of #137 and #190. Off unless a deployment supplies host, namespace, and task queue. A half-filled connection fails at boot, because a typo that quietly disabled the mirror is the same silent failure the drop counter exists to prevent. A dial failure only logs: Temporal being down must never stop this service answering. The worker that runs ToolTrajectoryWorkflow is the deployment's, the credential is #444, and which lane sets the variables is what keeps this Deep only. closes #887 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Kai Siren <coilysiren@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>