Feelers¶
octomate.tentacles.feelers.base ¶
Base channel feeler aggregate surface.
Feelers
dataclass
¶
Per-channel human-interaction surface.
Source code in octomate/tentacles/feelers/base.py
present
async
¶
What the graph reports outside a run, as a message: a move's announcement, or a failure's trace id. A move that leaves no line says nothing here.
Source code in octomate/tentacles/feelers/base.py
present_actions
async
¶
present_actions(
address: ChannelAddress,
event: ActionBatchEvent,
*,
action_manager: DeferredActionManager,
) -> None
A batch put up for a run that is not streamed, as the channel's cards.
Source code in octomate/tentacles/feelers/base.py
octomate.tentacles.feelers.output ¶
Output rendering shared by every channel: markdown chunking, stream batching, the timeline state a run is rendered onto, and the default feelers for channels with no live transport.
StreamBlockType
module-attribute
¶
SKIPPED_PLAN_TOOL_NAMES
module-attribute
¶
SKIPPED_PLAN_TOOL_NAMES = frozenset(
{
ASK_QUESTIONS_TOOL_NAME,
DEFAULT_OUTPUT_TOOL_NAME,
GatewayTool.SEND,
GatewayTool.COMMISSION,
GatewayTool.WHISPER,
GatewayTool.TELEPORT,
}
)
MarkdownChunker
dataclass
¶
Splits markdown into chunks under a platform's message limit, preferring paragraph, line, sentence and word boundaries in that order.
Source code in octomate/tentacles/feelers/output.py
chunk ¶
Source code in octomate/tentacles/feelers/output.py
split_index ¶
Source code in octomate/tentacles/feelers/output.py
last_separator_boundary ¶
Source code in octomate/tentacles/feelers/output.py
last_sentence_boundary ¶
last_whitespace_boundary ¶
StreamBlock
dataclass
¶
One block of a streamed reply — answer, thinking, tool call — by id.
Source code in octomate/tentacles/feelers/output.py
BatchedTextUpdate
dataclass
¶
A flushed edit to one block: the new delta, the full text so far, and whether the block is final.
Source code in octomate/tentacles/feelers/output.py
StreamEventDelta
dataclass
¶
A stream event reduced to text, tagged with the block it belongs to.
Source code in octomate/tentacles/feelers/output.py
TextStreamBuffer
dataclass
¶
Text accumulated for one block between flushes.
Source code in octomate/tentacles/feelers/output.py
TextStreamBatcher ¶
Coalesces streamed text per block into paced updates, by interval and size.
Source code in octomate/tentacles/feelers/output.py
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 | |
push_text ¶
Source code in octomate/tentacles/feelers/output.py
flush_block ¶
Source code in octomate/tentacles/feelers/output.py
finish_all ¶
Source code in octomate/tentacles/feelers/output.py
reset ¶
Drop accumulated buffers so the same batcher can back the next message
without carrying over full_text (which drives the fold threshold). The
configured pacing (flush_interval/min_chars/...) is preserved.
Source code in octomate/tentacles/feelers/output.py
full_text ¶
should_flush ¶
Source code in octomate/tentacles/feelers/output.py
StreamFlusher ¶
Single-flight background flusher for streaming renders (thinking/answer,
on every channel). signal() wakes a lone background task that runs the
flush callback — which sends whatever has accumulated; deltas that arrive
while a flush is in flight coalesce into the next pass, so the drive loop
never blocks on a slow platform round-trip. drain() stops the task; the
caller sends any final/settled state itself afterwards.
Source code in octomate/tentacles/feelers/output.py
MarkdownFeeler ¶
Bases: Protocol
Presents markdown to IM and returns platform message metadata only.
Return IMMessageID values for IM bookkeeping, such as a message id,
timestamp, or card id. Feelers must not return agent/runtime results.
Source code in octomate/tentacles/feelers/output.py
SubagentTimelineState ¶
One independently rendered commissioned child run.
Source code in octomate/tentacles/feelers/output.py
append_response
async
¶
Source code in octomate/tentacles/feelers/output.py
OpenSubagentTimeline
dataclass
¶
A subagent timeline held open: its context manager and the state its events land on.
Source code in octomate/tentacles/feelers/output.py
TimelineState ¶
Streaming render hook for one run, and the event→render dispatch (drive)
that walks the run stream. The feeler that opened the state calls bind with
the per-run context (address + the deferred-action feelers), then hands the stream
to drive, which maps one lifecycle method per real stream event:
PartStart/PartDelta/PartEnd of the thinking and answer parts map to
*_start / *_delta / *_end, and the tool call/result events to
tool_start / tool_end. A per-run timeline subclasses this and overrides
only the lifecycle points it actually renders; the base methods render
nothing. message_id carries the platform id of the rendered reply once
the run's context closes.
Mid-run notice contract: answer hooks that render reply text set noticed,
and every hook that opens a NEW timeline entry (a thinking block, a tool
call, a todo update — never a tool result, so in-flight work finishes where
it began) calls begin_entry() first, which rotate()s once when a notice
streamed in between. Overrides must keep doing both.
Source code in octomate/tentacles/feelers/output.py
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 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 | |
subagent_timelines
class-attribute
instance-attribute
¶
open_subagent
async
¶
Ignore child activity when this timeline has no streaming renderer.
Source code in octomate/tentacles/feelers/output.py
start_subagent
async
¶
Open an accomplice's own timeline; a renderer that fails is logged and never interrupts the parent's stream.
Source code in octomate/tentacles/feelers/output.py
finish_subagent
async
¶
Source code in octomate/tentacles/feelers/output.py
drive
async
¶
The single event→render dispatch over a run's wire events, as the nerve carries them; each typed event is drawn onto this state, which renders itself.
Source code in octomate/tentacles/feelers/output.py
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 | |
present_actions
async
¶
Render a deferred-action batch as a unit, record each presented action's platform message id, then settle the surface.
Source code in octomate/tentacles/feelers/output.py
actions_presented
async
¶
A deferred-action batch was just presented in this timeline's thread.
A pending action does not mean the run has stopped: a live run carries on past it. Hooks may flush buffered output, but must keep active thinking and answer surfaces open for more events. Drain background flushers before flushing their buffers here to avoid overlapping writes. The base renders no status, so there is nothing to settle.
Source code in octomate/tentacles/feelers/output.py
begin_entry
async
¶
A new timeline entry is opening: if answer content streamed since the last rotation, that content was a mid-run notice — rotate first.
Source code in octomate/tentacles/feelers/output.py
rotate
async
¶
A mid-run notice happened: finalize the current timeline surface (plan message, cards, buffered text) so the notice stays visible where it streamed, and let the new entries open a fresh surface below it. Work still in flight finishes — and closes — the previous surface.
Source code in octomate/tentacles/feelers/output.py
thinking_start
async
¶
thinking_delta
async
¶
Source code in octomate/tentacles/feelers/output.py
thinking_end
async
¶
Source code in octomate/tentacles/feelers/output.py
answer_start
async
¶
Source code in octomate/tentacles/feelers/output.py
answer_delta
async
¶
answer_end
async
¶
Source code in octomate/tentacles/feelers/output.py
answer_segment
async
¶
One completed message segment from a segment-list output. Platforms with a media transport override this to render images/cards natively; the default renders the segment's text form.
Source code in octomate/tentacles/feelers/output.py
tool_start
async
¶
tool_end
async
¶
todo
async
¶
capture_reply ¶
Apply the first reply target seen this run; ignore later replies.
Source code in octomate/tentacles/feelers/output.py
message_sent
async
¶
A send tool delivered these segments mid-run. Render them as
reply content (images/cards land natively via answer_segment), then
begin_entry() to rotate the just-rendered notice out as its own message —
the run continues afterward, so the final reply lands separately.
Source code in octomate/tentacles/feelers/output.py
oauth_authorization
async
¶
An integration is waiting for this user to authorize it.
Rotates the open entry first: the authorization goes out as its own message — a card where the platform has them — so whatever streamed before it stays above rather than merging into it.
Source code in octomate/tentacles/feelers/output.py
artifact_available
async
¶
Flush preceding output so the card separates it from the next entry.
The artifact has its own ledger row and never supplies message_id for
the final reply. It is not answer text and does not suppress that reply.
Source code in octomate/tentacles/feelers/output.py
TimelineFeeler ¶
Bases: Protocol
Opens a per-run TimelineState for a channel. The per-channel Feelers.timeline
is a single stateless instance: the channel's siphon does
async with feelers.timeline.open(address) as state: — entering acquires the
platform resource and yields the per-run hook (a card, a stream session, …), or
the feeler itself when there is nothing to set up; state.drive(events) then
renders each event onto it; exiting releases the resource and sets message_id.
Source code in octomate/tentacles/feelers/output.py
DefaultMarkdownFeeler ¶
Sends markdown through a channel's chromo and ink as plain messages.
Source code in octomate/tentacles/feelers/output.py
present
async
¶
SegmentsFeeler ¶
Bases: Protocol
Sends completed output segments to a conversation as one platform message.
Platforms with native media transport (e.g. NapCat images/files) implement this to deliver segments natively; the default joins their text forms.
Source code in octomate/tentacles/feelers/output.py
DefaultSegmentsFeeler ¶
Sends output segments through a channel's chromo and ink, one message per payload.
Source code in octomate/tentacles/feelers/output.py
present
async
¶
Source code in octomate/tentacles/feelers/output.py
DefaultTimelineState
dataclass
¶
Bases: TimelineState
The timeline for a channel with no live transport: collects the answer and todos, and sends them as one message when the run ends.
Source code in octomate/tentacles/feelers/output.py
parts
class-attribute
instance-attribute
¶
answer_delta
async
¶
Source code in octomate/tentacles/feelers/output.py
answer_segment
async
¶
todo
async
¶
Source code in octomate/tentacles/feelers/output.py
rotate
async
¶
send_parts
async
¶
Source code in octomate/tentacles/feelers/output.py
DefaultTimelineFeeler ¶
Bases: TimelineFeeler
Answer-only timeline for platforms with no streaming transport (NapCat).
Stateless; open(address) yields a fresh DefaultTimelineState. Thinking/tool
are inherited no-ops; text deltas are accumulated and native segments are
preserved, then sent on exit (a mid-run notice rotates out as its own
message), with a warning that streaming was unavailable.
Source code in octomate/tentacles/feelers/output.py
segments
instance-attribute
¶
open
async
¶
Source code in octomate/tentacles/feelers/output.py
render_stream_event_delta ¶
render_stream_event_delta(
event: AgentStreamEvent | AgentRunResultEvent[OutputT],
) -> StreamEventDelta | None
Source code in octomate/tentacles/feelers/output.py
tool_result_text ¶
format_stream_value ¶
Source code in octomate/tentacles/feelers/output.py
should_skip_plan_tool ¶
format_field_name ¶
humanize_tool_name ¶
truncate_task_detail ¶
format_inline_value ¶
Source code in octomate/tentacles/feelers/output.py
format_mapping_lines ¶
Source code in octomate/tentacles/feelers/output.py
format_list_lines ¶
Source code in octomate/tentacles/feelers/output.py
format_fields ¶
render_todo_lines ¶
User-facing checklist lines (- [x] … task-list markdown, plaintext-safe):
an in-progress item shows its active form, subtasks indent under their parent.
Source code in octomate/tentacles/feelers/output.py
markdown_from_output ¶
markdown_from_output(
output: JsonValue
| Sequence[MessageSegment]
| DeferredToolRequests,
) -> str | None
Source code in octomate/tentacles/feelers/output.py
split_reply ¶
Take the first ReplySegment anywhere in the list as the reply target (its
data.id is passed to the platform as reply_to); the body is the remaining
segments with every reply stripped out, so none renders as [reply: …] text.
No reply segment leaves the list untouched.
Source code in octomate/tentacles/feelers/output.py
present_markdown
async
¶
present_markdown(
*,
ink: Ink[MessageT],
chromo: Chromo[RawT, MessageT],
address: ChannelAddress,
markdown: str,
reply_to: str | None = None,
) -> IMMessageID | None
Source code in octomate/tentacles/feelers/output.py
octomate.tentacles.feelers.artifacts ¶
Artifact overviews rendered on the channel where they were created.
ArtifactFeeler ¶
Bases: ABC
Resolve an owned overview and record its delivery separately from the reply.
The original address is preserved, including its platform thread. Recipients
see the saved title and provider; opening the viewer still requires ownership.
Without a configured viewer origin, channel overviews are disabled.
Subclasses implement only the platform presentation in send.
Source code in octomate/tentacles/feelers/artifacts.py
present
async
¶
Source code in octomate/tentacles/feelers/artifacts.py
send
abstractmethod
async
¶
Present already-authorized metadata without reading app content.
PlainTextArtifactFeeler ¶
Bases: ArtifactFeeler
A saved title, provider name, and literal viewer URL.
Source code in octomate/tentacles/feelers/artifacts.py
send
async
¶
Source code in octomate/tentacles/feelers/artifacts.py
octomate.tentacles.slack.feelers.artifacts ¶
Slack artifact overviews, with a link to the authenticated viewer.
SlackArtifactFeeler ¶
Bases: ArtifactFeeler
Show a native card with a sparkle icon, saved title/provider, and Open button.
Title and subtitle are bounded by Slack's 150-character card limits. No app resource is fetched; the viewer still checks ownership when the link opens.
Source code in octomate/tentacles/slack/feelers/artifacts.py
send
async
¶
Source code in octomate/tentacles/slack/feelers/artifacts.py
octomate.tentacles.lark.feelers.artifacts ¶
Lark artifact cards linking to the authenticated web viewer.
LarkArtifactFeeler ¶
Bases: ArtifactFeeler
Show a blue title header, provider subtitle, and a native viewer button.
Titles and provider names remain plain text. The JSON 2.0 button opens the viewer directly, without a card callback or another upstream tool call.
Source code in octomate/tentacles/lark/feelers/artifacts.py
send
async
¶
Source code in octomate/tentacles/lark/feelers/artifacts.py
octomate.tentacles.discord.feelers.artifacts ¶
Discord artifact cards linking to the authenticated web viewer.
DiscordArtifactFeeler ¶
Bases: ArtifactFeeler
A Components V2 container with a title, provider, and viewer link button.
Saved metadata is bounded and escaped for Discord's Markdown text displays. The link opens the viewer directly without an interaction callback.
Source code in octomate/tentacles/discord/feelers/artifacts.py
send
async
¶
Source code in octomate/tentacles/discord/feelers/artifacts.py
octomate.tentacles.feelers.deferred ¶
Plain-text fallbacks for human-in-the-loop deferred action feelers.
QUESTION_PROGRESS_SUFFIX
module-attribute
¶
QUESTION_PROGRESS_SUFFIX = re.compile(
"\\s*\\(?\\s*Question\\s+\\d+\\s+of\\s+\\d+\\s*\\)?\\s*$",
re.IGNORECASE,
)
ApprovalFeeler ¶
Bases: ABC
Presents approval actions for one response target.
Source code in octomate/tentacles/feelers/deferred.py
present
abstractmethod
async
¶
QuestionFeeler ¶
Bases: ABC
Presents a card-based question wizard for one response target.
Source code in octomate/tentacles/feelers/deferred.py
present
abstractmethod
async
¶
PlainTextApprovalFeeler ¶
Bases: ApprovalFeeler
Presents approvals as markdown text, for channels without interactive cards.
Source code in octomate/tentacles/feelers/deferred.py
present
async
¶
present(
address: ChannelAddress, actions: list[DeferredApproval]
) -> dict[UUID7, IMMessageID | None]
Source code in octomate/tentacles/feelers/deferred.py
PlainTextAskQuestionFeeler ¶
Bases: QuestionFeeler
Presents questions as markdown text, for channels without interactive cards.
Source code in octomate/tentacles/feelers/deferred.py
present
async
¶
present(
address: ChannelAddress, actions: list[DeferredQuestion]
) -> dict[UUID7, IMMessageID | None]
Source code in octomate/tentacles/feelers/deferred.py
question_text ¶
answer_text ¶
An answer as one line of text, for a card to show: a multi-select question's picks are listed.
Source code in octomate/tentacles/feelers/deferred.py
octomate.tentacles.feelers.oauth ¶
Presenting a pending OAuth authorization, and the plain-text fallback.
An authorization is a two-step errand for the user: open the provider's page with a one-time code, then tell the agent so it can finish the connection. The event reaches this feeler as the authorization itself rather than as rendered text, so a channel with interactive cards can put the link on a button; channels without cards fall back to the markdown feeler.
Where it lands is this layer's decision, not each channel's: present resolves the
private address and a channel only fills in send, so there is no way for one to
render an authorization onto an address it chose itself.
AuthorizationEvent
module-attribute
¶
OAuthFeeler ¶
Bases: ABC
Presents one pending authorization for one response target.
Source code in octomate/tentacles/feelers/oauth.py
present
async
¶
The only way an authorization reaches a channel.
Concrete so the private address is settled here for every channel; a
subclass fills in send and never gets to pick where it sends.
Source code in octomate/tentacles/feelers/oauth.py
deliver_to
async
¶
Where this authorization has to land.
The code authorizes one person's account, so a request made from a group must not be read out to the group: it goes to that person's direct messages instead. A platform with nowhere private to reach them gets an error rather than the group, which is the whole point of asking.
What makes a surface private is shared, not the chat type: an assistant
pane is a thread only its own user can read, and moving the link out of it
would strand "return here and tell me to confirm" in another surface.
Source code in octomate/tentacles/feelers/oauth.py
send
abstractmethod
async
¶
Render this authorization onto address, which present has already
made private. Never call this with an address of your own.
Source code in octomate/tentacles/feelers/oauth.py
PlainTextOAuthFeeler ¶
Bases: OAuthFeeler[MessageT]
Presents an authorization as a markdown link, with the device code when there is one.