Skip to content

feat(dgw): generate a session log with AI - #2008

Draft
irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 13 commits into
feat/task-apifrom
feat/ai-log-task
Draft

irvingouj@Devolutions (irvingoujAtDevolution) wants to merge 13 commits into
feat/task-apifrom
feat/ai-log-task

Conversation

@irvingoujAtDevolution

@irvingoujAtDevolution irvingouj@Devolutions (irvingoujAtDevolution) commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Fills in the ai-log task: DVLS starts it, Gateway turns the session's terminal recording into text, asks the AI what the user did, and adds the answer as a new log-{n}.slog in the manifest logs list.

  • .cast and .trp, read as a stream (new TrpOutputReader), so memory is bounded by the chunk size, not the recording size; WebM fails as unsupported for now
  • chunks go to a task workspace under <RecordingPath>/.provisioner-tasks/<task-id>/ (same access as recordings), deleted when the task ends and cleaned up at startup
  • each chunk's answer is checkpointed, so a retry only re-sends the chunks that didn't finish; an answer cut at the output limit splits the chunk in half and retries
  • only output is sent to the AI, never keystrokes; ANSI stripped, lines prefixed with [seconds]
  • the log uses the shipped .slog format, session.start gets source: "ai", model, promptVersion; progress shows as running { step: "describing", done, total }
  • the manifest is read and the log appended through the recording manager, so it can't race a push; sessions still recording are refused

Also adds Error::Truncated to the AI crate (#2005) for answers cut at the token limit. Includes #2003 (merged in), since the log has to land in logs.

Tested: tasks 15/15 (end-to-end against a mock provider incl. resume + no key in any file), tasks:: lib 32/32, recording:: 13/13, gateway_ai 14/14, dvls_compatibility 24/24. A real run with OpenAI gpt-6-luna on a 9-min Ubuntu recording passed before the streaming rework; not re-run yet.

Stacked on #2007.

🤖 Generated with Claude Code

A .slog pushed for a session that already has a recording is now listed
in a new `logs` field of recording.json, named `log-{n}.slog`.
The `files` list keeps holding recordings only, so released players and
streamers keep working.
A log-only session (AD Console) still lists its .slog in `files`.
The `logs` field is omitted when empty, so existing manifests stay the same.
The session ZIP download now includes the logs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The recording manager appends the log as the next log-{n}.slog in the
manifest logs list, so it never races with a push rewriting the manifest.
Sessions still recording and sessions without a recording are refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The packet decoding is shared with the live stream decoder, so a
finished recording can be turned into asciicast without tailing it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ai-log task turns the terminal recordings of a finished session
(asciicast, or TRP through the terminal streamer decoder) into a timed
plain-text transcript, sends it to the AI provider in chunks of about
100k input tokens, and appends the actions as a new .slog log of the
session. The log uses the shipped .slog schema; session.start also
carries source, model and promptVersion. Video-only sessions fail with
an unsupported recording type error.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A running ai-log task now reports step describing with the number of
transcript chunks already sent and the total, and the OpenAPI document
and generated clients describe the new substate.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A post-session .slog push kept the session "connected", so /shadow
streamed the finished recording instead of refusing like master did.
/shadow now closes with "streaming ended" when the ongoing push is a
log, and log chunks no longer wake /shadow streamers.

The ongoing push now tracks which manifest entry it writes to
(recording or log, by index) instead of a bool, which removes the
"no log file" bug branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The ZIP now holds logs as well as recordings, so "clip" no longer fits.
The public endpoint doc is unchanged, so the OpenAPI output is too.

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

A truncated answer silently loses its last actions, so the caller gets
Error::Truncated and can ask again with a shorter input.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The recording is no longer read into memory. The task streams .cast and
.trp output into transcript chunk files in its workspace
(<RecordingPath>/.provisioner-tasks/<task-id>/), saves the actions of
each chunk as a checkpoint so a retry resumes at the first chunk
without one, and merges the checkpoints into the .slog. A chunk whose
answer is truncated is asked again in two halves, down to a floor.

The workspace is deleted when the task finishes, and stale ones are
removed when the task system starts. The manifest is read through the
recording manager, which also copies the finished log into the session.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant