Skip to content

Action Recognition: new task, model ABC, and workflow block - #2854

Draft
leeclemnet wants to merge 66 commits into
mainfrom
feat/cosmos3-edge-action-recognition
Draft

Action Recognition: new task, model ABC, and workflow block#2854
leeclemnet wants to merge 66 commits into
mainfrom
feat/cosmos3-edge-action-recognition

Conversation

@leeclemnet

@leeclemnet leeclemnet commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds Action Recognition: a video task that assigns class labels to frame ranges, and the workflow block that runs it over a stream. Output is (start_frame_idx, end_frame_idx, class) tuples, which can overlap. Net diff: 30 files, +4506/−23.

One output contract covers both kinds of checkpoint, so the block never knows which it holds. A zero-shot model returns a verdict for the whole window. A fine-tuned model returns spans inside the window.

Model layer (inference_models)

  • New task action-recognition, the ActionRecognitionModel ABC, and the frozen ActionRecognitionPrediction result. A VideoSampling contract travels with the model and carries window length, sample rate, minimum frames, sampling mode, frame budget, and trained frame side.
  • Cosmos3EdgeActionRecognition implements the ABC over the pure-VLM reasoner and picks its mode from the package. A tokenizer with <|cls:...|> tokens is a fine-tune; anything else runs zero-shot.
  • Zero-shot: one call per window, greedy, thinking off, 256 tokens, native resolution. With classes it asks a lettered multiple choice and reads the letter. Without classes it asks for one short phrase. About 0.7 s per call on an L4 at 2 s windows.
  • Fine-tune (packages from roboflow-train#880/Outlier Detection Block #884): the training contract verbatim — SFT system prompt, class-token legend, and <|cls:name|> <S.SS> <E.EE> span lines. Decoding runs through a prefix_allowed_tokens_fn state machine that admits only tokens continuing a valid line, so the answer always parses. Frames are capped at the trained side. Classes come from class_names.txt, and sampling from inference_config.json. A package with class tokens but no class list raises CorruptedModelPackageError rather than loading as zero-shot.
  • The reasoner gained LoRA package loading: base weights under base/, the class-token tokenizer on top, and an embedding resize before the adapter attaches. It also gained enable_thinking and constrained-decoding support on the video path, and real VideoMetadata so the model's frame timestamps match the clip.

Workflows layer (inference)

  • New block roboflow_core/roboflow_action_recognition_model@v1 and a tensor sibling. model_id is the standard model field; class_filter restricts a fine-tune's classes or forms the zero-shot vocabulary.
  • Window length and sample rate are training decisions, so the block reads them from the model. stride_seconds is the only temporal setting, and it defaults to the window, which tiles the stream without overlap.
  • The timeline only grows: same-class ranges merge when no sampled frame lies between them, and the block never extends a range on its own. window_classes lists classes that a later call can still merge with, and error_status reports a failed call without ending the stream.
  • Blocks now classify what they still hold when a stream ends, through the existing stream-pipeline flush. Without it a clip shorter than one window produced nothing. The WebRTC worker flushes at the point where it reports completion, and it now reads the handler result correctly when a workflow holds a pipelined block.
  • New kind action_recognition_prediction with its serializer and deserializer, and ffmpeg in the dev GPU image, without which upload FPS detection failed silently.

Known limit. Zero-shot classifies a whole window, so its boundaries land on window edges. In-window localization is the fine-tune path.

Related Issue(s): consumes roboflow-train#880 and #884.

Type of Change

  • New feature (non-breaking change that adds functionality)

Testing

  • I have tested this change locally
  • I have added/updated tests for this change

Test details:

Unit tests, all green in the CUDA staging container: 51 in the model layer (mode routing, lettered and phrase parsing, the fine-tune prompt and span format, per-call class subsets, the constrained-decoding automaton, the package readers, the malformed-package error, per-mode frame resolution) and 76 on the block (manifest, fire cadence, float sampling grid, FPS pinning, monotone merging, keep-alive window_classes, whole-video mode, end-of-stream flush, resets, tensor paths). The stream handler suite adds 23, and the visualization suite 14.

Live validation on an L4, through the workflow path with real weights:

  • A fine-tuned checkpoint on its 15.5 s training clip (labels: out 0–8 s, in 8.5–15.5 s) returns out and in with boundaries inside half a second of the labels, at the 8 s window its package declares. A 6 s clip, shorter than that window, is classified whole by the end-of-stream flush; before the flush it returned nothing.
  • Zero-shot on a 21.5 s traffic clip detects the overtake with no false positives across about 40 calls, and window_classes stays on for the event and clears when the range closes.
  • Both tensor and numpy paths run, with ENABLE_TENSOR_DATA_REPRESENTATION on and off.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code where necessary, particularly in hard-to-understand areas
  • My changes generate no new warnings or errors
  • I have updated the documentation accordingly (if applicable)

Additional Context

The prompt and decode settings come from measurements against NVIDIA's own demo assets, not preference. Zero-shot cannot localize inside a window under a cheap decode: the SFT span contract, the cookbook dense-caption prompt, and the guide's timestamp query all collapse to positional templates without a think block, and neither a larger token budget, nor thinking, nor NVIDIA's sampling parameters, nor lower resolution recovers it. Thinking-on dense captioning does localize, at 20–90 s per call, which is why an earlier two-stage design was dropped. The fine-tune trains that ability into the cheap path, so the shipped zero-shot mode buys classification and the fine-tune buys boundaries.

Testing ran on an L4 staging build. Implementation used Codex agents for two of the larger changes, with review and live validation here.

🤖 Generated with Claude Code

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thanks for the pull request! Here is how automated Claude review works here, so you spend credits (and reviewer time) wisely.

🚧 Right now this is a draft, so automated Claude review is paused — nothing is being spent yet. Mark it Ready for review to trigger it.

Warning

💸 The Claude reviewer bills in credits, not vibes

Automated review spins up a real agent that reads real code and spends real credits on every pass. It is glad to help — but it is not a rubber duck, a linter you poke in a loop, or a substitute for reading the contributing guide. Treat it like an expensive senior reviewer whose time you booked, and show up prepared.

Draft when unsure, Ready when you mean it:

  • 🌱 Not sure the PR is in good shape yet? Keep it (or set it back) as a draft — drafts pause review, so you can push and iterate without burning credits on a moving target.
  • 💪 Feel strong about the contents? Mark it Ready for review and the reviewer will take a look.

However you get there, arrive prepared:

  • 🧱 Bring a SOLID, thorough PR. Point your local agent at our skills/ to tune it to our guidelines first — or, if you are one of those fabled carbon-based contributors, read them yourself. A half-baked diff costs exactly the same to review as a finished one.
  • Resolve every comment before you re-request review. Re-requesting with threads still open means paying twice for the same conversation.
  • 🔁 Do not use CI review as an inner loop for a local agent. The reviewer is not a step-by-step debugger — do the unfolding locally and arrive with the answer, not the search.
  • 🙋 If something looks off, ask a human. One question to a maintainer is cheaper and faster than three rounds of agent re-review chasing a misread.

Reviews are not free. A draft costs nothing to review; a Ready PR is a promise that it is worth reviewing.

  • Prefer to skip automated review entirely? Add the skip-claude-review label.

@leeclemnet leeclemnet changed the title Cosmos3 video classification: new task, temporal localization, workflow block [WIP] Cosmos3 video classification: new task, temporal localization, workflow block Aug 25, 2026
@leeclemnet
leeclemnet force-pushed the feat/cosmos3-edge-action-recognition branch from 9ef7ef4 to 2f3768f Compare August 26, 2026 12:21
@leeclemnet leeclemnet changed the title [WIP] Cosmos3 video classification: new task, temporal localization, workflow block Cosmos3 video segment classification: new task, model ABC, workflow block Aug 26, 2026
leeclemnet and others added 26 commits August 28, 2026 18:18
…zation

Register cosmos-3-edge under the new video-multi-label-classification
task and add Cosmos3EdgeReasoner.temporal_localization: one prompt per
clip requesting JSON events in seconds, parsed into sampled-frame
index ranges (floor start, ceil end). The parser accepts numeric
seconds, mm:ss.ff strings, and the prompt guide's caption key. An
env-gated integration test is the Phase 0 prompt-format gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add the model-agnostic streaming video classification block
(roboflow_core/video_classification_model@v1, Cosmos3 by default) with
its tensor sibling, the video_multi_label_classification_prediction
kind backed by the typed VideoIntervalClassification entity, and
serializer/deserializer/loader wiring. The block samples tumbling
windows (seconds x fps, tracker-style fps fallback), calls
temporal_localization per window, and emits a cumulative timeline with
provisional open ranges plus per-frame active classes. Tail-window
classification at stream end needs an execution-engine end-of-stream
signal and ships separately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Prompt for integer frame indices instead of seconds, per review: the
platform contract is frame-indexed and unit conversion in the parser
adds failure modes before any observed need. An entry survives only
when both boundaries are plain JSON integers inside the sampled range;
timestamps, floats, numeric strings, and the caption key all drop the
entry. The Phase 0 gate tests this format first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Match the prior action_recogntion_block v2 conventions per Peter:
rename window_size_seconds/sampling_fps to window_seconds/sample_fps,
replace tumbling windows with a rolling buffer classified from the
first frame onward at a configurable stride_seconds (default
window_seconds / 2 for 50 percent overlap), and surface model-call
failures through a new error_status output instead of raising, so a
stream survives transient failures. Fire time is stamped before the
call so a failed call does not retry on every frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Future video classification models will be purpose-built, not VLM
wrappers, so nothing outside Cosmos3-specific code may depend on
temporal_localization. Add the VideoClassificationModel ABC and the
frozen VideoSegmentClassification result dataclass under models/base
(single abstract infer; the backend pre/forward/post split does not fit
API-wrapping models), and Cosmos3VideoSegmentClassification, which
implements infer by wrapping the reasoner. The registry's
video-multi-label-classification entry points at the wrapper; the block
types against the ABC and wraps a bare reasoner only for the hosted
cosmos-3-edge id, whose platform task type is vlm. Naming follows the
annotation tooling: Segment, not Interval, throughout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cosmos3EdgeReasoner is a pure VLM again (prompt / prompt_video only).
Cosmos3VideoSegmentClassification owns the temporal-localization prompt
template and the strict parser, which now returns
VideoSegmentClassification dataclasses directly, and calls the
reasoner's generic prompt_video. The Phase 0 integration gate targets
the segment classifier's infer().

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Apply the Segment nomenclature to the base module and ABC:
models/base/video_segment_classification.py defines
VideoSegmentClassificationModel. The workflow block class name is
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
By analogy with ClassificationPrediction: the model-layer dataclass and
the workflows entity are both named
VideoSegmentClassificationPrediction. The ABC and the Cosmos3 class
names are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Complete the Segment nomenclature sweep: the registry task becomes
video-segment-classification, and the kind and its serializers follow
(video_segment_classification_prediction). The pre-existing
multi-label-classification image task is untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
serialize_/deserialize_video_segment_classification_prediction_kind now
match the video_segment_classification_prediction kind, following the
kind-named serializer convention.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ation

Match the Cosmos3Edge* family naming (Cosmos3EdgeReasoner,
Cosmos3EdgeWorldModel) and the segment nomenclature in the module name:
cosmos3_video_segment_classification.py defines
Cosmos3EdgeVideoSegmentClassification. Test module renamed to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The block completes the Segment nomenclature sweep:
roboflow_core/video_segment_classification_model@v1, display name
"Video Segment Classification Model", class
VideoSegmentClassificationModelBlockV1, package
models/roboflow/video_segment_classification/. The type string is free
to change while unreleased.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A naming audit found stragglers the class renames missed: the block's
model_id description (which named Cosmos3's mechanism, not the task),
the GPU restriction note, the unsupported-model error, the tensor
sibling docstring, the changelog feature name, and the integration test
name. Search keywords keep "video classification" for discoverability.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per Lee's staging UI test: model_id becomes the generic-model-block
required RoboflowModelField; class_names becomes optional and moves to
Additional Properties, with vocabulary resolved block param ->
model-provided (fine-tunes read class_names from the package's
model_config.json) -> fully open (new open-vocabulary prompt,
class_id -1); active_classes now emits the standard multi-label
classification prediction shape so Classification Label Visualization
accepts it (and no longer accepts the timeline kind). Both prompt
templates cite NVIDIA's temporal-localization cookbook.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow the sibling base-class convention: VideoSegmentClassificationModel
declares class_names as an abstract property; implementations provide
it (Cosmos3 returns its stored vocabulary, None when zero-shot).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
class_names becomes class_filter, matching the core model blocks: for
fine-tuned models it restricts output to a subset of the model's
classes; for zero-shot models it forms the prompt vocabulary; empty
accepts all classes (open vocabulary). class_id indexes the model's
own class list when one exists (stable ids under filtering, as with
detection class filters), else the filter list, else -1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found via instrumented WebRTC UI runs: the reset signature included the
resolved source fps, and live streams re-estimate measured_fps
continuously, so every frame looked like a config change — state wiped
per frame, single-frame windows, empty timelines, and one ~7 s model
call per frame. Resolve fps once per video and store it in the
bookkeeping; the reset signature carries user configuration only. The
sam3 video blocks are immune because they never consume fps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Observed live: a fractional sampling stride (7.5 at 30 fps / 4 sample
fps) places samples alternately 7 and 8 frames apart, so adjacent
windows' reports sit ceil(stride) apart and the float tolerance missed
the merge by under one frame, fragmenting a continuous event into
window-aligned pieces. Regression test reproduces the observed
geometry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Give Cosmos3 exactly what it was trained on (per the technical report
and the cookbook processor sources): prompt_video accepts video_fps and
passes VideoMetadata with do_sample_frames=False, so the processor's
injected per-frame '<X.X seconds>' markers carry the real clock instead
of the assumed 24 fps; the segment classifier prompts for decimal
seconds at 0.1 s granularity (the trained localization format) and
converts to sampled-frame indices in the parser (floor start, ceil
end). The public frame-index contract is unchanged at every boundary.
The block documents the pixel/temporal trade: the model spreads a
fixed pixel budget across a call's frames.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
do_sample_frames=False leaves the Edge processor's sampled-frame
indices unset and its per-frame timestamp step crashes on None.
Passing num_frames equal to the frame count makes the sampler an
identity linspace, keeping indices populated and timestamps correct.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The checkpoint's processor config merges fps=2 into every call, which
collides with the identity num_frames; an explicit fps=None wins the
merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live finding on the full vehicles clip: window-spanning assertions
smear boundaries and bridge two distinct same-class events into one
range. Both prompt templates now instruct precise boundaries (start at
visibility, end when the event stops, not at clip edges) and separate
events per distinct occurrence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two live-path fixes from full-clip testing on the WebRTC UI runs:

The blind 30 fps fallback on a 25 fps stream skews the model's whole
time axis by 20 percent (windows, strides, and every reported second).
When the stream provides no fps, the block now pins a one-shot estimate
from the median of the first eight frame-timestamp deltas (clamped
1-120; the median shrugs off delivery stalls during model calls;
degenerate timestamps keep the 30 fallback). Sticky like the existing
pin; resets clear it.

A segment counted as open only when its end exactly touched the
window's last sampled frame, but the model rounds boundaries by roughly
10-20 percent of its window - ongoing events kept closing and
active_classes flickered. Openness is now a band:
max(0.15 x window frames, sampling stride). Recorded endpoints are
unchanged; absent classes still close immediately.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
measured_fps reports the pipeline's arrival rate. WebRTC ACK windows pace
delivery to processing, so after the first-frame model call it tracks model
latency (~0.05 fps observed). Pinning it collapsed stride_frames to 1 and
fired the model on every frame, which kept arrival slow — a self-
reinforcing loop. Only declared fps pins directly; otherwise the frame-
timestamp median estimates the source rate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every supported source declares its FPS: cv2-backed files, RTSP, and
webcams via CAP_PROP_FPS, and WebRTC uploads via the worker's ffprobe
detection. The only fps-less path observed was a dev image without
ffprobe, where frames arrive ACK-paced and timestamps carry no source
clock — there the estimator measured the delivery pipe (77.62 on a
25 fps clip). With no valid consumer left, declared fps pins and the
30 FPS warning fallback covers the rest.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
leeclemnet and others added 26 commits August 28, 2026 18:18
Same-class ranges unify only when no sampled frame lies in the gap.
ceil(sampling_stride) is that rule exactly on the sampling grid, not an
approximation: consecutive samples sit floor/ceil(stride) apart, and the
next possible sampled gap is about twice the stride.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cookbook comment refers to the localization prompt, so that prompt
now sits directly under it, ahead of the mapping templates. Comments
added during this branch shrink to the constraint they carry; the
experiment history lives in the PR and commit messages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
class_filter no longer forms a prompt vocabulary — detected events are
classified into it. The window floor now appears on the window_seconds
field, the finite-clip geometry (a window at or beyond the clip length
never fires) is documented, and error_status gets a sentence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Other architectures, possibly non-VLM, will serve this task, so the
prose names model properties (pixel budget, temporal floor) instead of
cosmos-3-edge. The 2 s default window sat below the prototype model's
floor, so a fresh block returned empty timelines out of the box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pixel budgets and temporal floors are properties of the prototype
model, not the task. The block describes only what it controls: frames
per call, temporal context per classification, and result frequency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
to_legacy_classification_prediction assumed every input is a tensor-
native prediction object and crashed on the legacy dict shape that
Video Segment Classification's window_classes emits. Input already in
the target shape passes through unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Under ENABLE_TENSOR_DATA_REPRESENTATION every classification producer
emits a tensor-native prediction; the tensor sibling emitted the legacy
dict and crashed the visualization's converter. window_classes now
builds a MultiLabelClassificationPrediction per the multi-label block
convention: dense confidence over the vocabulary with detected ids, or
a per-prediction dynamic class map for open vocabulary. The numpy block
keeps the legacy dict. This reverts the converter's dict passthrough —
contract violations fail loudly again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The WebRTC output path imported the numpy wildcard serialiser
statically, so under ENABLE_TENSOR_DATA_REPRESENTATION tensor-native
outputs (window_classes, native detections) reached orjson unserialised.
Its default encoder then returned unknown objects unchanged, which
orjson retries forever — the run died with "default serializer exceeds
recursion limit" after one frame. The import now mirrors the loader's
flag swap, and the default encoder raises a TypeError so future gaps
fail with a clear message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No-think mapping misattributes subjects in multi-object captions: on
the vehicles captions it assigned the overtake to the wrong caption
across several vocabulary phrasings, and open-vocab condensing swapped
subjects the same way. With thinking, attribution is correct at both
tested budgets — but think plus one JSON entry per caption outgrows 512
tokens at nine captions, so the mapping shares the 4096 localization
budget. Measured stage-2 cost: 12-50 s per fire by caption count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sandbox enabler: single-call no-think experiments need the switch on
prompt_video, which only prompt_text carried.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Monotone timelines: the display equals the stored union; the open-range
provisional extension and the openness band are gone. window_classes
derives from range mergeability — a class stays listed while a future
positive fire could still merge with its range (one window plus the
sample tolerance) and clears when the range closes. Defaults become the
sliding operating point (2 s window, 0.5 s stride) and stride is always
explicit; boundary precision equals the stride.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…contract

Zero-shot now makes one no-think call per window: lettered choice over
the vocabulary, or a short-phrase label open-vocabulary. Timelines
emerge from the block merging per-window verdicts; the two-stage
localization pipeline and its prompts are removed. Models fine-tuned on
the roboflow-train#880 contract (class tokens in the tokenizer) speak
that contract instead: its system message and prompt, no-think at 256
tokens, span-line answers parsed with the trainer's grammar, and
constrained decoding via a prefix_allowed_tokens_fn state machine so
answers parse by construction and labels stay in vocabulary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-shot

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trainer ships adapter layouts under the dash-less cosmos3-edge slug
with task vlm: base weights under base/, the class-token tokenizer and
adapter at the top, and no resized embedding table. The loader now
registers that slug, grows the embeddings to the tokenizer before the
adapter attaches, and merges. Class names derive from the added
<|cls:...|> tokens in token-id order; video_pre_processing from
inference_config.json sets the fine-tune frame cap. The parser strips
the chat end token that skip_special_tokens=False keeps on the final
span line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Window length and sample rate are training decisions, not deployment
settings: the ABC gains a VideoSampling contract (16 s / 4 fps /
4-frame floor by default), the Cosmos3 wrapper fills it from the
package's video_pre_processing, and the block reads it from the loaded
model instead of exposing window_seconds. Stride stays a block
parameter. Fires wait for the model's minimum frame count.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A clip shorter than one window never reached a scheduled call, and longer
clips lost the frames after the final call. The block now joins the stream
pipeline at depth 0 and classifies the pending tail on end of stream, which
matches the trainer: it classifies a shorter-than-window clip whole, down to
the model's minimum frame count.

The pipeline gated draining on the RF-DETR depth flag; it now detects any
handler that exposes flush(), so a depth-0 block reaches the sinks. The
handler keeps the last frame batch to carry that flush result.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roboflow-train#884 ships the platform's class_names.txt convention, in
class-token order. Prefer it, and keep deriving names from the class
tokens for checkpoints trained before it. The model_config.json lookup
is gone: that file is the loader's own offline manifest, so it never
carried a class list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
roboflow-train#884 records mode: a whole_video checkpoint reads a clip as
one unit, with its frame budget spread over the full length, so fixed
windows misrepresent it. These models now skip the stride schedule: the
buffer holds a thinned set of frames spanning the stream, capped at the
trained frame budget and keeping the first and newest samples, and the
end-of-stream flush classifies it. Thinning lowers the real frame rate,
so the call reports the rate the buffer carries.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The trainer emits window_frames next to window_seconds, and derives the
seconds from frames over fps. In whole_video mode the length has no
window meaning at all: frames is the budget drawn from the entire clip.
Read window_frames when the package declares it, and keep deriving it
from the window length otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The team settled on Action Recognition for the task, and on cosmos3-edge
with the vlm task type for fine-tune packages. Renames the workflows
block to roboflow_core/action_recognition_model@v1, the kind to
action_recognition_prediction, the registry task to action-recognition,
and the model layer to ActionRecognitionModel /
ActionRecognitionPrediction / Cosmos3EdgeActionRecognition, with the
modules and tests moved to match.

The dash-carrying cosmos-3-edge id keeps resolving to the plain reasoner
under the vlm task, because the Cosmos3 VLM block and the legacy adapter
both load it that way; the block wraps that reasoner for action
recognition at load time. Fine-tune packages ship as cosmos3-edge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every block under core_steps/models/roboflow uses
roboflow_core/roboflow_<task>_model@vN. Display names carry no prefix,
so only the type changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only fine-tune packages ever carry the action-recognition task, and they
ship under the dash-less cosmos3-edge slug. The hosted cosmos-3-edge base
is registered as vlm and resolves to the plain reasoner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@leeclemnet
leeclemnet force-pushed the feat/cosmos3-edge-action-recognition branch from 9d3e835 to 1b80acc Compare August 28, 2026 20:49
leeclemnet and others added 2 commits August 28, 2026 18:48
roboflow-train now ships class_names.txt in every package, so deriving
the class list from the tokenizer is dead. The wrapper also held the
package sampling twice, as a raw dict and as the typed contract; it now
keeps only VideoSampling, which gained the trained frame side.

Also fixes the WebRTC worker, which read the handler result as a list.
A workflow holding a stream-pipelined block runs behind a handler that
returns an InferenceHandlerResult instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A package whose tokenizer carries class tokens but ships no
class_names.txt used to load as a zero-shot model, which serves the
wrong prompt and decoder against fine-tuned weights. It now raises
CorruptedModelPackageError. A base model without class tokens still
loads as zero-shot.

The WebRTC worker also drove blocks to the end of a stream without ever
flushing them, so a clip shorter than the model's window produced
nothing. Every path there ends by announcing completion, so the flush
runs at that point and sends what the block still holds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@leeclemnet leeclemnet changed the title Cosmos3 video segment classification: new task, model ABC, workflow block Action Recognition: new task, model ABC, and workflow block Aug 28, 2026
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