Skip to content

feat(temporal): Split a step into a model call, one activity per tool call, and a seal. - #7

Closed
moetemp wants to merge 1 commit into
moe/worktree-freshnessfrom
moe/l2-tool-activities
Closed

moetemp wants to merge 1 commit into
moe/worktree-freshnessfrom
moe/l2-tool-activities

Conversation

@moetemp

@moetemp moetemp commented Aug 26, 2026 •

Copy link
Copy Markdown
Collaborator

This PR splits one step of a session turn into three activities instead of one, behind OPENCODE_TEMPORAL_STEPPED=1. Off by default.

Until now the smallest durable unit was a step: one model call plus every tool it asked for, in a single activity. Nothing could sit between the model asking for a tool and the tool running, so per-tool retry policies, per-tool timeouts, approval gates and budgets had nowhere to live.

runModelCall  ->  runToolCall (one per call, concurrent)  ->  sealStep

SessionRunner.runModelCall performs the attempt, records each call as Tool.Called, and hands the calls back instead of running them. runToolCall settles one call. sealStep takes the end snapshot, diffs it against the start, and publishes Step.Ended. The loop between them is workflow code (stepped-turn.ts). The supervisor is untouched: it only ever called one runTurnStep, so the stepped mode supplies a different one.

What has to hold, and has a test for it:

  • One owner token per step, not per activity. The event log fences a publish behind the current owner. Only runModelCall claims; the tool and seal activities publish under the token it returns. Claiming is a compare-and-set, because two attempts of one activity can be alive at once and a paused attempt 1 resuming after attempt 2 must not take the log back.
  • A call that already started is not silently repeated. runToolCall publishes Tool.Called before it runs the tool, so a call the log shows as running is one a dispatch was already inside. Only a tool declaring idempotent runs again; anything else reaches the model as an unknown outcome. One admission event, with an id derived from the call, admits one dispatch under overlapping attempts.
  • The tool arguments come off the log, not the hand-off. The record holds the provider's raw JSON, which the dispatcher parses; a provider that delivers a call whole has its arguments seeded into the record.
  • A stop closes the calls it cut short, and a hand-off does not. A declined permission crosses the boundary as its own failure type, the step is sealed on the way out with no continuation, and a cancellation that means this attempt is being handed on leaves the call as it was found.
  • Each tool ships the tree from the host that ran it, because the seal can land on any worker. Where two hosts could run one step's tools, they run one at a time (OPENCODE_TEMPORAL_SERIAL_TOOLS, on by default only with a shared store).

Verified live against a Temporal dev server and gpt-5-mini: a one-tool turn recorded five activities and the tool ran once; four concurrent read calls ran as four overlapping runToolCall activities with no fenced write; a serve killed mid-tool came back on a fresh worker with the interrupted call reported unknown and the model recovering; an interrupt closed the in-flight call by itself. The SessionExecution conformance suite passes in both Temporal modes and in the local coordinator, and gained the two scenarios the split changes: a tool round trip, and an interrupt with a tool in flight.

Worker affinity, the live event tick, the worktree freshness rule and the fork CI workflow, which used to ride in this PR, are their own PRs in the stack now.

@github-actions

Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions

Copy link
Copy Markdown

Hey! Your PR title Split a step into model-call, per-tool, and seal activities. doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions

Copy link
Copy Markdown

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

@github-actions github-actions Bot closed this Aug 26, 2026
@moetemp moetemp reopened this Aug 26, 2026
@moetemp moetemp changed the title Split a step into model-call, per-tool, and seal activities. feat: split a step into model-call, per-tool, and seal activities. Aug 26, 2026
@github-actions

Copy link
Copy Markdown

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

@github-actions

Copy link
Copy Markdown

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

@github-actions github-actions Bot closed this Aug 26, 2026
@moetemp moetemp reopened this Aug 26, 2026
@moetemp
moetemp force-pushed the 2026/08/opencode-temporal branch from 289f93b to 3434d80 Compare September 7, 2026 23:27
@moetemp
moetemp force-pushed the moe/l2-tool-activities branch from de37977 to d2db32f Compare September 7, 2026 23:27
@moetemp
moetemp force-pushed the 2026/08/opencode-temporal branch from 3434d80 to abca188 Compare September 14, 2026 18:11
@moetemp
moetemp force-pushed the moe/l2-tool-activities branch from d2db32f to a5b5d00 Compare September 14, 2026 18:11
@moetemp
moetemp force-pushed the 2026/08/opencode-temporal branch from abca188 to 542f4c2 Compare September 14, 2026 22:34
@moetemp
moetemp force-pushed the moe/l2-tool-activities branch from a5b5d00 to 10af0ac Compare September 14, 2026 22:34
@moetemp
moetemp force-pushed the 2026/08/opencode-temporal branch from 542f4c2 to 1e2f3e7 Compare September 15, 2026 00:44
@moetemp
moetemp force-pushed the moe/l2-tool-activities branch from 10af0ac to 039a3b1 Compare September 15, 2026 00:44
@moetemp moetemp changed the title feat: split a step into model-call, per-tool, and seal activities. feat(temporal): Split a step into a model call, one activity per tool call, and a seal. Sep 15, 2026
@moetemp
moetemp changed the base branch from 2026/08/opencode-temporal to moe/worktree-freshness September 15, 2026 00:46
A whole-step activity holds the provider attempt and every tool under one timeout and one retry policy, so a tool waiting on a human or hanging takes the attempt with it. Behind `OPENCODE_TEMPORAL_STEPPED=1` the workflow drives the three units itself, under one owner token per step, with the tool arguments read off the log and a stop closing the step it cut short.
@moetemp
moetemp force-pushed the moe/l2-tool-activities branch from 039a3b1 to c5cfa3b Compare September 15, 2026 01:01
@moetemp
moetemp force-pushed the moe/worktree-freshness branch from 827a2fc to a497d3b Compare September 15, 2026 01:01
@github-actions

Copy link
Copy Markdown

This pull request has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.

Feel free to open a new pull request that follows our guidelines.

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.

1 participant