> For the complete documentation index, see [llms.txt](https://adaptix-framework.gitbook.io/adaptix-framework/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://adaptix-framework.gitbook.io/adaptix-framework/development/teamserver-interface/events-and-hooks-1.md).

# Frame Manager

Listeners do not buffer multi-chunk agent transfers themselves. The teamserver `FrameManager` owns two directions for a numeric session id:

* **Upstream.** The agent sends a file or a large check-in in pieces. The listener calls `TsFramePut`, `TsFramePutDecoded`, or `TsFramePutStream`. The first time the call reports the object complete, the returned buffer is the assembled body. A later put for that same finished session returns complete and a nil body.
* **Downstream.** The listener asks `TsFrameGetChunk` or `TsFrameGetChunkSticky` for a slice of hosted tasks at the offset it passes. `TsFrameAckDelivery` confirms bytes; it does not move a cursor. `TsFrameResetUpstream` and `TsFrameResetDownstream` drop a half-finished transfer.

`TsFrameHasPending` tells a listener whether a session still has hosted work, so an agent can sleep instead of polling an empty queue.

## Modes

`TsFramePut` and `TsFramePutDecoded` select the mode from the arguments.

<table><thead><tr><th width="201.83331298828125">Call</th><th width="136.0999755859375">Mode</th><th>Use</th></tr></thead><tbody><tr><td><code>TsFramePut</code> / <code>TsFramePutDecoded</code>, <code>chunkCount</code> above 0</td><td>Chunk index</td><td>Pieces are numbered. <code>sackBitmap</code> marks up to 32 chunks after the next expected index.</td></tr><tr><td><code>TsFramePut</code> / <code>TsFramePutDecoded</code>, <code>chunkCount</code> zero and <code>totalSize</code> above 0</td><td>Byte offset</td><td>Pieces are a byte range in a known total. <code>sackBitmap</code> is always 0. The next hole is walked in steps of the first chunk's size.</td></tr><tr><td><code>TsFramePutStream</code></td><td>Stream</td><td>Pieces are a <code>seqNum</code> from 0 plus an <code>isLast</code> flag.</td></tr></tbody></table>

`TsFramePut` returns `complete`, `nextExpected`, `received`, `sackBitmap`, and `assembled`. The body is set only the first time that object completes. Listeners put `sackBitmap` into the protocol ack only for chunk index.

`TsFramePutDecoded` runs the caller decode on that body. If the callback returns an error or a nil body, `complete` stays true and `assembled` is nil.

The default upload cap is 4 MB. Byte offset rejects a larger `totalSize` immediately. Chunk index and stream return no body once the assembled buffer would pass the cap.

### What the listener still does

1. Authenticates the agent and calls `TsAgentSetTick`.
2. Decrypts one protocol frame.
3. Hands the plaintext piece to `TsFrame*`.
4. On a completed upstream object, calls `TsAgentProcessData`. A nil body after `TsFramePutDecoded` is not a body to dispatch.
5. Writes the downstream slice from `TsFrameGetChunk` or `TsFrameGetChunkSticky` back in its own protocol.

Build this only inside a listener extender. Agent plugins keep using `PackTasks` and `ProcessData`; the server calls those around the frame manager.

## Downstream

`TsFrameGetChunk` and `TsFrameGetChunkSticky` return `total`, `chunkOffset`, `data`, `taskNonce`, and `isEmpty`. The offset in the call is the slice copied out of the stored data. Concurrent calls for one session pack that data once. The default size passed to `TsAgentGetHostedAll` is 4 MB; `maxChunkSize` then slices it. An `encode` callback runs before the data is stored.

`isEmpty` true means there is nothing to send. Sticky mode is for a lossy transport: repeating the same offset does not drop the session when the attempt limit is passed. The default limit is 10. Without sticky, passing it drops the session and returns no data with `isEmpty` false. A new offset resets the attempt count.

`TsFrameAckDelivery` ignores a non-zero nonce that does not match. A nonce of 0 skips that check. A matching ack clears the attempt count. The session is dropped only when `ackOffset` covers the data. A `GetChunk*` whose offset is already at or past the end drops the session and then packs whatever hosted work is left.

When a `GetChunk*` finds no download session and the hosted queue is empty, unfinished `TASK_TYPE_TASK` and `TASK_TYPE_FEATURE` entries that still have data are put back on the queue before the pack. `TsFrameHasPending` counts an open download session, the hosted queue, pivot tasks, and those same unfinished entries.

A request at offset 0, after the last byte of the current data was already served and more hosted work exists, drops that session and packs the next data.

## Progress, notes, and reset

A stored upstream piece, the first `GetChunk*` of an offset, an `AckDelivery` that finishes the data, and either reset call `TsAgentIoProgress`. A repeated byte offset, a stream sequence that has already been appended, and an ack that does not finish the data do not. The teamserver sends that broadcast at most every 250 ms, and always when the active flag changes. A listener does not call `TsAgentIoProgress` itself.

After `GetChunk*` or a completed put, the listener should call `adaptix.NoteFrameSent` or `adaptix.NoteFrameRecv`. Each helper takes its stat once (`TsFrameTakeStatTasks`, `TsFrameTakeStatRecv`). Sent stats exist only after a served slice reaches the end of the data, and the size is the packed length. The console line looks like `Sent 12.00 Kb (in 4 requests)` or `Received …`. Units are `bytes`, `Kb`, `Mb`, and `Gb`. `(in N requests)` is added only when N is greater than 1. A receive under 256 bytes in one request, or a non-positive size, is omitted. An empty sent selection, or a packed size of 0, is omitted too.

`TsFrameResetUpstream` drops an in-progress upload and leaves the finished-upload cache, so a repeat of that finished object still returns complete and a nil body until the cache expires. `TsFrameResetDownstream` drops the download session. Canceling an upload calls `TsFrameResetDownstream`, so the agent stops reading the canceled bytes. `TsTaskCancel` does the same for a task already in `RunningTasks`. Finalizing a managed feature that was already running does the same. A task that was still only queued does not.

About once a minute the manager drops an upstream session with no new piece for 5 minutes, and a finished-upload cache entry 5 minutes after completion. A download session is dropped after 10 minutes without a chunk, and a non-sticky one is also dropped once it is over the attempt limit. Those intervals and the 4 MB caps are the defaults.
