Skip to content

rescue phase 2: Commission.resume + avp-ollama warm-rescue support - #12

Draft
Patrick Carney (pcarney8) wants to merge 5 commits into
rescue/spec-and-ollamafrom
rescue/phase-2-spec-and-ollama
Draft

rescue phase 2: Commission.resume + avp-ollama warm-rescue support#12
Patrick Carney (pcarney8) wants to merge 5 commits into
rescue/spec-and-ollamafrom
rescue/phase-2-spec-and-ollama

Conversation

@pcarney8

Copy link
Copy Markdown

Summary

Stacked on #10 (rescue/spec-and-ollama). AVP-side phase 2 (warm rescue) — extends the spec + types + avp-ollama runner to support Commission.resume. Paired with portofcontext#3 which is the supervisor side.

  • Spec (commission.md §2.2/§2.3/§2.4): new optional Commission.resume field, ResumeBlock shape (context + tool_cache + in_flight_tool_call), determinism contract for rescued runners.
  • Types (avp.types): ResumeBlock, ResumeContext, ResumeMessage, ToolCacheEntry, ToolCacheFailure, InFlightToolCall, plus RunResumedEvent + RunResumedData (the second supervisor-sourced bracket event, companion to RunRescuedEvent from rescue: spec extensions + avp-ollama runner package #10).
  • avp-ollama (translator.py): reads Commission.resume, skips prelude on resume, seeds messages from resume.context.messages instead of prompt, short-circuits tool invocations that match resume.tool_cache.
  • Test instrumentation: OLLAMA_INJECT_USER_MESSAGE_AT=turn:N:content (introduces a recall token mid-conversation; T2 smoke uses this) and OLLAMA_INJECT_TOOL_CALL_AT=turn:N:tool:args_json (forces a tool invocation; T3 smoke uses this). Both are demo-only — same shape as RESCUE_FAIL_AT.
  • Stub tool registry: echo(args) → args — exists so the T3 smoke can prove the cache-replay path end-to-end without real model tool-calling. Real tool dispatch comes when avp-ollama grows real tool support.

Test plan

  • cd python && uv run pytest avp/tests/ -q — 116 passing, no regressions.
  • cd python && uv run pytest agents/avp-ollama/tests/ -q33 passing (+22: 12 inject-directive parsing, 10 resume-block reading).
  • Pydantic round-trip: ResumeBlock(...).model_dump_json(by_alias=True) and RunResumedEvent(...).model_dump_json(by_alias=True) both produce valid envelopes.
  • End-to-end T2 + T3 via portofcontext's make demo-warm-rescue-local (depends on supervisor side merging).

Out of scope (deferred)

  • Reasoning-block continuity across rescue. text_emitted carries assistant text but not extended-thinking content; the secondary runner loses chain-of-thought continuity. Acceptable for v0.1; documented in RESCUE_PLAN_PHASE_2.md §2.
  • True in_flight_tool_call resumption. v0.1 surfaces it as informational; phase 2.x lands the actual resumption protocol.
  • avp-claude-agent warm-rescue support. Bigger lift (CASDK-driven); ships as a separate PR.

🤖 Generated with Claude Code

Patrick Carney (pcarney8) and others added 2 commits May 13, 2026 15:52
Phase 2 (warm rescue) AVP extensions, additive:

  * commission.md §2.2 — new optional Commission field `resume:
    ResumeBlock | null` set by the supervisor when re-dispatching a
    rescued run. Absent on fresh dispatches; ignored agents degrade
    gracefully to cold rescue.

  * commission.md §2.3 — `ResumeBlock` shape:
      from_seq, replay_policy ∈ {context_only, skip_completed},
      context.messages[], tool_cache[], in_flight_tool_call?
    Plus nested ToolCacheEntry (result | failure, tier:
    idempotent/replay_only/needs_approval) and InFlightToolCall.

  * commission.md §2.4 — determinism contract for rescued runners:
    MUST NOT emit seq <= from_seq, MUST skip the prelude, MUST seed
    its model call with context.messages, MUST honor tool_cache on
    match, SHOULD respect tier on miss.

  * types.py — pydantic models for everything above:
    ResumeBlock, ResumeContext, ResumeMessage, ToolCacheEntry,
    ToolCacheFailure, InFlightToolCall. Plus RunResumedEvent +
    RunResumedData (the second supervisor-sourced bracket event,
    paired with the phase-1 RunRescuedEvent).

  * T_RUN_RESUMED constant + Event union extension.

Existing 116 avp tests still pass. New types verified via standalone
round-trip serialization.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ooks

Phase 2 §5.1 — avp-ollama honors the warm-rescue contract from
commission.md §2.4. Plus test instrumentation to make warm-rescue
behaviors mechanically observable in the smoke.

Translator changes:

  * `_extract_resume_block(config)` — pull `Commission.resume` from
    the Commission. None on fresh dispatches; dict otherwise.
  * `_seed_messages()` — prefers `resume.context.messages` over
    `_initial_messages()` when present. The rescued runner's model
    call sees the reconstructed conversation, not just the original
    prompt.
  * `_resume_messages()` / `_resume_tool_cache()` — defensive
    extractors that filter malformed entries.
  * `run()` — when `resume_block` is present, use `from_seq` as the
    starting seq (skip the next_seq HTTP round-trip), still inherit
    the agent span, still skip prelude. Falls back to next_seq query
    for legacy cold rescues that pre-date phase-2 supervisor work.
  * `_lookup_tool_cache(name, args)` — finds matching cache entries
    by `tool_name + canonical-equal args` (Python ==; the supervisor
    pre-canonicalizes both halves, so equality holds for normal
    payloads).
  * `_emit_tool_outcome_from_cache(parent_span, entry)` — emit
    `tool_returned` (or `tool_failed`) using the cached payload.
    Runner does NOT call the tool.
  * Stub tool registry: `_execute_stub_tool("echo", args)` returns
    args verbatim; anything else returns a synthetic payload describing
    what would have happened. Real tool dispatch comes when avp-ollama
    grows real tool support.

Test instrumentation (demo only, same shape as RESCUE_FAIL_AT):

  * `OLLAMA_INJECT_USER_MESSAGE_AT=turn:N:content` — after turn N
    completes, inject `{role: user, content}` AND emit a
    `text_emitted{role: user}` event so the supervisor's resume
    reconstruction picks it up. T2 smoke uses this to introduce a
    recall token mid-conversation.
  * `OLLAMA_INJECT_TOOL_CALL_AT=turn:N:tool_name:args_json` — before
    turn N, emit `tool_invoked` for the configured tool + args. Hit:
    cached `tool_returned` from `resume.tool_cache`. Miss: stub
    `echo` registry. T3 smoke uses this to prove cache replay.
  * Both parsed at construction; both no-op when unset; both lenient
    on bad input (warn + disable rather than crash).

Tests: 22 new (12 inject-directive parsing, 10 resume-block reading).
Lib total 33 (was 11).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@pcarney8 Patrick Carney (pcarney8) added the enhancement New feature or request label May 13, 2026
Patrick Carney (pcarney8) and others added 3 commits May 13, 2026 17:27
The supervisor inserts events into the trajectory concurrently with
the runner — specifically `avp.run_resumed` (after the rescued
runner's first agent event lands) and `avp.run_rescued` (during the
rescue path itself). When the supervisor takes the seq the runner
was about to write, the runner's POST returns 409 Conflict and the
event is lost.

Observed in `make demo-rescue-local`: secondary runner's
text_emitted at seq=10 got 409'd because the supervisor's
avp.run_resumed landed at seq=10 in the same instant. The model's
actual post-rescue answer never made it into the trajectory.

Fix: _post now handles 409 specifically. On collision it re-queries
the supervisor for next_seq, bumps the local counter past it, and
retries. Bounded to 3 attempts. Non-409 HTTP errors keep the
original log-and-continue behavior (best-effort).

Other HTTP-error types unchanged — only 409 triggers retry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CPU-only Ollama with a long warm-rescue prompt can spend >2 minutes
generating a single turn, which trips the old 120s httpx timeout —
the runner then emits execution_backend_failure that looks like a
real rescue trigger but is actually "model is too slow." Both runners
hit this in the warm-rescue smoke (primary at 11:27 → 11:29:46, then
the rescue target inherits the prompt and hits the same wall).

Two knobs:
  OLLAMA_NUM_PREDICT  cap on output tokens (passed as options.num_predict)
                      to /api/chat. 0/unset preserves prior model-decides
                      behavior for GPU users.
  OLLAMA_HTTP_TIMEOUT httpx timeout in seconds. Default bumped 120 → 300.

README documents both alongside the existing env-var table.
5 minutes wasn't enough for the warm-rescue secondary on CPU Ollama:
each post-rescue turn carries 5+ messages of context, and per-token
latency grows with prompt length. Observed: a single chat completion
on the rescue-target hit exactly 5m0s and was killed by httpx, which
the supervisor saw as `execution_backend_failure: ollama transport:
timed out`. 600s gives headroom; pair with OLLAMA_NUM_PREDICT to keep
real turns well under this ceiling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant