Native session hooks and tailers¶
octomate.tentacles.hooks ¶
FastAPI dependencies that authenticate a runtime's hook request and resolve its sender.
hook_guard ¶
Authenticate a hook request against API tokens with the hooks scope.
The dependency yields the owner's username for ledger attribution.
Source code in octomate/tentacles/hooks.py
hook_sender ¶
hook_sender(
users: UserManager,
runtime: str,
verify: Callable[[str | None], Awaitable[str]],
) -> Callable[[str], Awaitable[UserProfile]]
The guard's second half, as a FastAPI dependency: the verified principal
resolved to their own profile on runtime's pseudo-channel — who every
ledger row this request writes is attributed to.
Composes over verify rather than re-reading the header: FastAPI caches a
dependency's value per request, so a router-level guard and this one share a
single bearer check.
Source code in octomate/tentacles/hooks.py
octomate.tentacles.claude.ingest ¶
Hook-driven ingest of a native Claude Code session.
Hooks establish session and run identities and signal transcript boundaries. Only the transcript tail writes messages.
ClaudeHookIngest ¶
Establish native session/run identities and relay transcript boundaries.
Hooks may arrive before or after their transcript. They ensure empty runs under the same native turn keys, but never write messages or replace a run's contents. The tail alone creates model messages and their ledger bindings. Its authenticated stream supplies the sender. A hook's transcript path is client context, never a server-local file to open.
Source code in octomate/tentacles/claude/ingest.py
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 | |
handle
async
¶
Handle an authenticated hook; message attribution belongs to the stream.
Source code in octomate/tentacles/claude/ingest.py
on_user_prompt_submit
async
¶
Source code in octomate/tentacles/claude/ingest.py
on_stop
async
¶
Source code in octomate/tentacles/claude/ingest.py
on_session_end
async
¶
Source code in octomate/tentacles/claude/ingest.py
on_subagent_start
async
¶
Source code in octomate/tentacles/claude/ingest.py
on_subagent_stop
async
¶
Source code in octomate/tentacles/claude/ingest.py
ensure_subagent_run
async
¶
Ensure the child's empty run without copying its hook prompt.
Source code in octomate/tentacles/claude/ingest.py
start_session
async
¶
Ensure the session's skeleton (thread + conversation), under the session lock so the write can't race the hooks' first-sighting create. The transcript itself arrives only through the stream; nothing here follows the path a hook claims.
Source code in octomate/tentacles/claude/ingest.py
session_thread
async
¶
This session's thread, filed under the project already holding the directory it is running in — and under none when no project holds it.
The session's own directory is what says which project this is, not the
transcript path, which lives in Claude's tree whatever the code does. Nothing is
registered from here: Claude's own projects are a context store keyed by
directory, not a claim that a tree is worked in, so a Claude session is not
evidence of a project the way a Codex workspace is. The directory is recorded on
the run regardless, which is what a later promotion would read.
The project lands only when the thread is created, so resolving it on every later hook costs a path comparison and rewrites nothing.
Source code in octomate/tentacles/claude/ingest.py
ensure_run
async
¶
Reserve the turn's foreign-key target; repeated or late hooks change nothing.
Source code in octomate/tentacles/claude/ingest.py
octomate.tentacles.locks ¶
Per-session asyncio locks that clean up after themselves.
SessionLocks ¶
Per-session asyncio.Locks. Not a lock of its own — a thin registry over the
stdlib lock, adding the keying the stdlib has no primitive for, plus self-cleanup:
each entry is a weak reference, so once no task holds or waits on a key's lock it is
reclaimed and the key leaves nothing behind.
async with locks.hold(key) releases on every exit — return, exception, or
cancellation — so an acquire cancelled by its timeout frees the lock automatically,
no manual dismissal or force-release.
Source code in octomate/tentacles/locks.py
by_session
instance-attribute
¶
hold
async
¶
Hold the key's lock for the block. acquire_timeout bounds the wait for the
lock and nothing else: a waiter stuck behind a wedged holder raises
TimeoutError rather than blocking forever, while the block it guards runs
unbounded once entered. A caller wanting to bound the work too wraps the whole
async with in its own cancel scope; a never-acquired lock is never released.
Source code in octomate/tentacles/locks.py
octomate.tentacles.tailer ¶
The shared lifecycle of a native turn published from a transcript.
NativeTurn
dataclass
¶
One transcript turn's live events and prompt bindings.
Tailers supply native boundaries, source message identities, and their own accumulator's message list. This owner rejects committed or driven turns, records prompts in that list, publishes the fold, and settles only after the caller commits the run. Subagent transcripts do not publish on this surface.
Source code in octomate/tentacles/tailer.py
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | |
prompts
class-attribute
instance-attribute
¶
open
async
classmethod
¶
open(
*,
run_id: str,
recorded: set[str],
conversations: ConversationManager,
threads: ThreadManager,
locks: SessionLocks,
address: ChannelAddress,
sender: UserProfile,
impulses: ObjectSendStream[Impulse],
messages: list[ModelMessage],
) -> NativeTurn | None
Publish the opening bracket unless this turn already has an owner.
Source code in octomate/tentacles/tailer.py
prompt
async
¶
prompt(
*,
message_id: str,
text: str,
timestamp: datetime,
metadata: JsonObject | None = None,
) -> None
Record a source prompt once and deliver its request at this point.
The ledger row grants access before the run is complete. Its id is also the enqueue id, so the console reconciles the live prompt with that row.
Source code in octomate/tentacles/tailer.py
fold
async
¶
Consume and publish the accumulator's events on the native thread.
Source code in octomate/tentacles/tailer.py
close
async
¶
Bind the committed history before publishing the closing bracket.
The caller holds the session lock through its commit and this binding.
record_external_run preserves input order; resolve the request objects
against that committed list here instead of tracking indices in tailers.
A failed commit or binding propagates without publishing run_ended.
Source code in octomate/tentacles/tailer.py
octomate.tentacles.claude.tailer ¶
Assembly of native Claude Code turns from a streamed transcript.
Lines streamed in by octomate claude tail feed ClaudeTranscriptTailer, which
commits each completed turn as an ExternalAgentRun.
OpenTurn
dataclass
¶
The turn currently being assembled off the live tail — its accumulator fed line
by line, its byte range advancing to cover the last transcript line folded in. It
commits as an ExternalAgentRun when the next prompt line or its Stop hooks'
summary line closes it, when its own Stop hook reaches stop_turn, or at
finalize.
Source code in octomate/tentacles/claude/tailer.py
SubagentTail
dataclass
¶
One subagent transcript's cursor within a session's tail. A subagent writes its
own file (<session>/subagents/agent-<agentId>.jsonl), so it gets its own byte
cursor, conversation, and open turn. Its turns frame on promptId change: subagent files
carry no promptSource, and a resumed subagent's next turn may open on a
tool-result line, not a prompt.
Source code in octomate/tentacles/claude/tailer.py
TailState
dataclass
¶
One streamed session: its transcript lives on the client machine that streams
it (octomate claude tail); the stream route feeds feed_remote, which drives
the per-line assembly. transcript_path is the client's claim in the client's
own namespace, a label never opened here — as are the subagent tails' paths.
Source code in octomate/tentacles/claude/tailer.py
ClaudeTranscriptTailer ¶
Assembles Claude turns from transcript lines streamed in by octomate claude
tail — the only assembler: the server never opens a transcript, even for a
session on this machine. Hooks can reserve empty runs, but only the transcript
produces model messages and ledger rows.
Each framed line feeds a ClaudeRunAccumulator — the same translation the live
tentacle and the whole-file restore use — driving two decoupled sinks:
- durable — one
ExternalAgentRunper completed turn, carrying the byte range it was built from, filling any empty run reserved by a hook. Older hook sketches are replaced for compatibility. Idempotent byprompt_id. - live — wire events sent through the connection's nerve publisher at the
native thread address, waiting for no reader. Only uncommitted parent turns publish:
run_started, the fold, thenrun_endedafter the reply is bound. A reconnect reopens an unfinished turn with anotherrun_started; no reflex is woken.
A turn commits on a proven boundary: the next prompt line, or the drained eof
a relayed Stop/SessionEnd asks the client for — the transcript is flushed
before Claude fires those hooks. Nothing newer than a stopped turn commits early
(drain_turn), and the tail process exits once the stopped turn is durable; the
next prompt's launcher spawns a fresh one. The committed runs' offsets are what
a reconnecting client is told to resume from, so a connection that drops
mid-stream leaves its open turn for the next connect to re-stream.
Source code in octomate/tentacles/claude/tailer.py
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 | |
stop_turn
async
¶
The turn just stopped — its Stop hook fired: drain it now instead of
waiting for the next prompt line to close it.
The stream route relays finalize on stop_event, the client ships its
final lines and answers eof, and finish_remote commits the stopped turn —
the tail process exits, and the next prompt's launcher spawns a fresh one.
Scoped by prompt_id so nothing newer than the stopped turn ever commits
early; without one there is nothing to scope, and the turn keeps its old
boundary. A session no stream covers has nothing to drain.
Source code in octomate/tentacles/claude/tailer.py
finalize
async
¶
End a session (SessionEnd): relay the drain to its stream — the route
watches stop_event and sends the client finalize; the client drains,
answers eof, and finish_remote commits the trailing turns and sets
drained. The wait is bounded: a client that is gone leaves its turns for
the next connect, and the SessionEnd hook must not hang on it. A session no
stream covers has nothing to finalize.
Source code in octomate/tentacles/claude/tailer.py
shutdown
async
¶
attach_remote
async
¶
attach_remote(
session_id: str,
transcript_path: Path,
sender: UserProfile,
address: ChannelAddress,
impulses: ObjectSendStream[Impulse],
) -> tuple[TailState, dict[str, int]]
Register a streamed session and answer where each of its files resumes.
The offsets are recomputed from the committed runs, per file: the session
transcript under SESSION_FILE, each known subagent
under its agent id — so the client holds no durable cursor and a reconnect is
just re-asking. A lingering registration (its client died without a close) is
replaced; the dead route feeds the state object it attached, and its commits
are idempotent.
Source code in octomate/tentacles/claude/tailer.py
feed_remote
async
¶
Advance a remote session by one framed line — the pumps' per-line body, against offsets the client measured in its own file. Takes the state rather than a session id so a superseded connection keeps feeding the state it attached, never whoever registered after it.
Source code in octomate/tentacles/claude/tailer.py
finish_remote
async
¶
A remote session's clean end — the client drained to EOF and said eof.
After the session's own end (finalize relayed at SessionEnd, or the idle
drain), commit everything still open. After a relayed Stop (drain_turn
set), commit only the stopped turn: a prompt queued into the drain window is
left open to re-stream whole on the next connect, and subagent turns keep
their own boundary — SubagentStop commits them, and force-closing here
would truncate a background child still running across turns. Either way,
release any finalize waiter and reclaim the registry slot.
Source code in octomate/tentacles/claude/tailer.py
detach_remote ¶
Drop a remote session's registration without committing anything: the connection died mid-stream, so its open turns' bytes were never provably complete. The next connect resumes from the committed offsets and re-streams them.
Source code in octomate/tentacles/claude/tailer.py
prepare
async
¶
Resolve the session's conversation and seed the committed-turn guard from it, so a resume never re-commits a run the last run already wrote.
Source code in octomate/tentacles/claude/tailer.py
ensure_session
async
¶
Map a native Claude session id to its Octomate home — the session's thread and the conversation hanging off it — creating either if this is its first sighting.
Source code in octomate/tentacles/claude/tailer.py
parse_line
staticmethod
¶
One typed line, or None for a blank / malformed / unmodeled one — skipped but never allowed to wedge ingest; the caller advances its cursor either way.
Source code in octomate/tentacles/claude/tailer.py
process_line
async
¶
Route one typed line: a prompt_source user line opens a turn (closing the
previous one); other user (tool-result) and assistant lines fold into the open
turn, and a stop_hook_summary closes it. Inline is_sidechain lines are
skipped — transcripts since 2.1.177 keep subagents in their own files
(pump_subagents), so this guards only against an older transcript's inline
relics.
Source code in octomate/tentacles/claude/tailer.py
record_title
async
¶
Carry the name Claude gave this session onto the rows that show it.
The transcript restates the title on every turn and revises it as the work turns out to be about something else, so the comparison — not the write — is what runs per line. The thread takes it too: a native thread is its session, and a name about the work beats the line the thread opened with.
Source code in octomate/tentacles/claude/tailer.py
harvest_subagent_call
async
¶
Remember which parent tool call spawned which subagent: an Agent call's
tool-result line names the child (toolUseResult.agentId) while its content
block names the call. Stamps child runs' parent_tool_call_id at commit.
Source code in octomate/tentacles/claude/tailer.py
fold
async
¶
Fold a line into the open turn: consume it (pushing its live events) and extend the turn's byte range and provenance to cover it.
Source code in octomate/tentacles/claude/tailer.py
record_permission_mode
async
¶
Keep the conversation's posture at what the session's own transcript says.
Every prompt line carries the mode that turn ran under, so a ⇧⇥ in the client reaches Octomate on the operator's next message. Observed, never set: nothing here can change a running client's mode, and the console shows it read-only.
A mode Claude has that this build's vocabulary does not is skipped rather than stored — the column is validated on read, so an unknown one would take the conversation out of circulation instead of merely being unrecognized.
Source code in octomate/tentacles/claude/tailer.py
begin_turn
async
¶
Open the native lifecycle at the transcript's prompt boundary.
Source code in octomate/tentacles/claude/tailer.py
close_turn
async
¶
Commit the open turn as an ExternalAgentRun with its byte range, then bind
the turn's human-ledger rows to it. Idempotent by prompt_id, so a re-driven
overlap after a resume is a safe no-op. Held under the session lock so the commit
cannot race a hook reserving the same turn.
A commit that cannot be made propagates instead of being skipped past. A
reconnect resumes from the last committed turn's end_offset, which only
points at the right bytes while the committed turns are the earliest ones:
skipping a turn and streaming on would let a later turn push that mark past
the gap, stranding the skipped turn where no re-stream could reach it.
Failing keeps the mark honest — the next connect re-streams the turn from
there and commits it.
Source code in octomate/tentacles/claude/tailer.py
subagent_tail
staticmethod
¶
Source code in octomate/tentacles/claude/tailer.py
finish_subagent
async
¶
Drain and commit one subagent on its SubagentStop — the child's
finalize, which a Claude child needs: its turns frame on promptId change,
so nothing in its own file closes the last one. The hook is synchronous but
the transcript writer is not: the final answer line races it (measured:
usually loses by ~1-2KB). The child's lines arrive through feed_remote on
their own; when the event names the answer, wait until the stream yields it,
bounded so a mismatch can never hang the pipe — tail.offset says whether
the writer has gone quiet. A session no stream covers has nothing to
settle.
Source code in octomate/tentacles/claude/tailer.py
subagent_settled
staticmethod
¶
The fast path: the drained turn already carries the exact answer the stop event announced. Divergence (a truncated or differently-joined hook payload — no contract promises byte equality) is not an error: the caller's byte-quiescence fallback settles those, so strictness here only ever costs one extra poll beat, never a wrong commit.
Source code in octomate/tentacles/claude/tailer.py
prepare_subagent
async
¶
Resolve the child's conversation under the session's thread and seed its committed-turn guard — the first time this child's lines are fed.
Source code in octomate/tentacles/claude/tailer.py
process_subagent_line
async
¶
process_subagent_line(
state: TailState,
tail: SubagentTail,
line: TranscriptLine,
start: int,
end: int,
) -> None
Frame a subagent's turns on promptId change — the only marker its file
carries (promptSource is absent, and every line is is_sidechain, so
neither parent rule applies). Each turn's promptId is the parent turn
that drove it; a resumed subagent opens its next turn on whatever user line
arrives first, which is measured to be a tool result, not a prompt.
Source code in octomate/tentacles/claude/tailer.py
begin_subagent_turn ¶
Source code in octomate/tentacles/claude/tailer.py
fold_subagent ¶
fold_subagent(
tail: SubagentTail,
line: TranscriptAssistantLine | TranscriptUserLine,
end: int,
) -> None
Fold a line into the child's open turn. Events are consumed, not emitted: the live stream is the parent timeline's; fanning child events into it would interleave two timelines under one label.
Source code in octomate/tentacles/claude/tailer.py
close_subagent_turn
async
¶
Commit the child's open turn as a child run: keyed <agentId>:<promptId>
(which subagent, which parent turn — a bare promptId would collide with the
parent run it belongs to), linked by parent_run_id = that promptId and the
harvested spawning call. A subagent has no human prompt or answer, so it never
touches the ledger.
Source code in octomate/tentacles/claude/tailer.py
close_subagent_turns
async
¶
Commit every child's trailing open turn — the subagent counterpart of the
final close_turn, at a session-ending drain (finish_remote).
Source code in octomate/tentacles/claude/tailer.py
stamp ¶
Date the messages a transcript record produced by that record's own clock — the
accumulator stamps now, which is wrong for a run replayed off disk
(AgentRun.started_at and displayed times read from these).
Source code in octomate/tentacles/claude/tailer.py
octomate.tentacles.deepseek.tailer ¶
Assembly of native dsh turns from a streamed session log.
History entries streamed in by octomate deepseek tail feed DeepseekEventTailer,
which commits each completed turn as an ExternalAgentRun.
PendingPrompt
dataclass
¶
One human user/message — the ledger row's raw material.
Source code in octomate/tentacles/deepseek/tailer.py
OpenTurn
dataclass
¶
The turn currently being assembled off the stream — its accumulator fed entry by entry, its seq range advancing to cover the last one folded in.
Source code in octomate/tentacles/deepseek/tailer.py
accumulator
class-attribute
instance-attribute
¶
TailState
dataclass
¶
One streamed session. Its log lives on the client machine that streams
it (octomate deepseek tail, reading that machine's dsh gateway); the
stream route feeds feed_remote, which drives the per-event assembly.
transcript_path is the client's claim in the client's own namespace, a
label never opened here.
Source code in octomate/tentacles/deepseek/tailer.py
DeepseekEventTailer ¶
Assembles native dsh turns from history entries streamed in by
octomate deepseek tail — the stream is the only assembler: the server
never opens a session log and never speaks to the client machine's dsh.
The client reads its local gateway (session.history, which serves the
zstd-framed log decoded and unpacked, cold sessions included) and ships
each entry as one framed line, the event's dense seq standing where a
file tail's byte offsets stand. Entries fold through the same
DeepseekRunAccumulator the driven tentacle uses; a turn commits on its
own turn/end line as an ExternalAgentRun keyed <session>:<turn>,
carrying the seq range it was assembled from — the committed floor a
reconnecting client is told to resume past. An open turn never commits at
a connection boundary: the next connect re-streams it whole.
Source code in octomate/tentacles/deepseek/tailer.py
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 | |
attach_remote
async
¶
attach_remote(
session_id: str,
transcript_path: Path,
cwd: str,
sender: UserProfile,
address: ChannelAddress,
impulses: ObjectSendStream[Impulse],
) -> tuple[TailState, dict[str, int]]
Register a streamed session and answer where it resumes: the seq after the committed floor, recomputed from the durable runs — the client holds no cursor of its own. A lingering registration (its client died without a close) is replaced; the dead route keeps feeding the state it attached, and its commits are idempotent.
Source code in octomate/tentacles/deepseek/tailer.py
feed_remote
async
¶
Fold one history entry; the route validates the sequence framing.
Source code in octomate/tentacles/deepseek/tailer.py
detach_remote ¶
Drop a streamed session's registration. Nothing commits on the way
out — a dsh turn closes on its own turn/end line, so a turn still
open here is mid-flight, and the next connect re-streams it whole from
the committed floor.
Source code in octomate/tentacles/deepseek/tailer.py
stop_turn
async
¶
The turn just stopped — its Stop hook fired: wait for the stopped
turn's streamed close to commit, then ask the drain. Runs detached
from the hook (dsh's stopping seam waits on the hook serially, ahead
of the very turn/end this waits for). A session no stream covers has
nothing to settle: its turns land at the next connect.
Source code in octomate/tentacles/deepseek/tailer.py
shutdown
async
¶
session_thread
async
¶
This session's thread, filed under the project already holding the directory it runs in — and under none when no project holds it. As on the Claude side, a dsh session is not evidence of a project, so nothing is registered here; the cwd is recorded on each run regardless.
Source code in octomate/tentacles/deepseek/tailer.py
process_entry
async
¶
Source code in octomate/tentacles/deepseek/tailer.py
266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 | |
record_prompt
async
¶
Keep each human input where it occurs, including mid-turn steering.
The event seq is stable across reconnects, even before dsh reports a turn number. Its ledger row grants access while the turn is still streaming.
Source code in octomate/tentacles/deepseek/tailer.py
fold
async
¶
Fold and publish one entry, dating messages by the log's clock.
Committed seqs never reach the fold. The native thread receives only run brackets, with the end delivered after its ledger binding commits. Re-streaming an open turn starts it again without waking a reflex.
Source code in octomate/tentacles/deepseek/tailer.py
commit_turn
async
¶
Source code in octomate/tentacles/deepseek/tailer.py
record_permission_mode
async
¶
Keep the conversation's posture at what the session log last said. Observed names are retained even when absent from the driven catalog.
Source code in octomate/tentacles/deepseek/tailer.py
committed_floor ¶
The highest event seq any committed turn covers — the stream resumes at the seq after it.
Source code in octomate/tentacles/deepseek/tailer.py
event_time ¶
stamp ¶
Date the messages an event produced by that event's own clock — the
accumulator stamps now, which is wrong for a turn replayed off the log
(AgentRun.started_at and ordering both read from these).
Source code in octomate/tentacles/deepseek/tailer.py
human_prompt ¶
The human's own words in a user/message, or None. dsh logs injected
user-role messages in the same turn — agent-instructions, plugin
runtime context — which are the harness talking, not the ledger's prompt;
human prompts and late question replies are the person's input.