Tools¶
octomate.capabilities.gateway ¶
Gateway capability: an agent's routing spellbook.
The spells decide where a turn goes, who handles it, and what it is about. Each is opaque on its own, so the instruction opens with plain words for what they actually do:
inspect: reveal agent routes, suggested addresses, or projects.summon: hand the conversation to another agent (a handoff — they take over from a brief). The graph reads the recorded decision after the run.teleport: continue the same agent in a new thread, carrying the history forward. Deferred, so the graph can fork the history and resume. With aproject, the place is that project's workspace — the door out of a throwaway tree into one whose work is kept — andnew_thread=falsebinds the current thread.dismiss: give a project thread's workspace back once the work in it is done. Recorded, and performed by the graph when the turn ends and its work is saved.commission: draw another agent into working a self-contained task in the background and return its report — an ordinary awaited tool call, never a deferral; the caller keeps the conversation and the user sees none of it.whisper: a quiet follow-up to an accomplice by name; it keeps its own context.send: deliver content mid-run without ending the turn, here or in the asking user's direct messages. It lives here rather than in its own capability because naming somewhere other than "here" is a routing decision, and this is where the channel registry and the run's own address already are.
The policy itself — what each spell may name, and the decision it records — lives on
octomate.managers.gateway.OctomateSession; this capability is Inkling's translation
of it into pydantic-ai: refusals become ModelRetry, a teleport becomes a deferral,
and a send rides the run event stream. The accomplice spells stay wholly here — they
spawn pydantic-ai runs, which no other runtime's gateway offers.
GATEWAY_INSTRUCTION_TEMPLATE
module-attribute
¶
GATEWAY_INSTRUCTION_TEMPLATE = "## Gateway — decide where this conversation goes and who handles it\n\nThese tools route the conversation. Default to handling it yourself: if you can answer\nwell or do the work, do it and call none of them. Routing is the exception — reach for a\ntool only when one of the signals below clearly fires.\n\n### Addresses and discovery\nFor `{teleport}`, `destination` is a `ChannelAddress` object, or\n`destination=null` to use the current conversation's address. An address carries\n`channel_tentacle_id`, `chat_type`, `chat_id`, `user_id`, and optional\n`channel_thread_id` and `shared` visibility. Use known identifiers and preserve\nvisibility; `user_id` must match the requesting user's linked identity on that\nchannel. When no current conversation is attached, supply an explicit address.\n\n`{inspect}` with `reveal=\"destinations\"` suggests addresses without creating anything.\nTo look further, add `channel` to browse one connected channel a level at a time:\nan address with `metadata.inside` is a place to open by passing that value as\n`inside`, and one without it can host a new thread unless `metadata.barred` says\nwhy not. A channel that cannot be browsed says so.\nYou can also supply a known address directly; discovery is optional, and each\noperation validates the address independently. Suggestions are not an exhaustive\nlist or a guarantee that every spell can use each address.\n`new_thread=true` is the default: create a thread at the address. For creation,\nuse `channel_thread_id=null`; an existing thread cannot contain another thread.\n\n### `{summon}` — hand off to another agent\nSummon transfers the conversation to a specialist who takes over this turn *and its\nfollow-ups*: a real, sticky handoff, so the bar is high. Summon only when:\n- The request needs a capability you lack — e.g. running or editing code in a real\n repository or environment, or a domain another agent is described for.\n- It is substantial specialist work another agent would do markedly better, not\n something you can handle from what you already know.\n\nDo NOT summon when:\n- You can already answer or do it — length or a technical-sounding topic is not a reason.\n- You are only mildly unsure — ask the user a clarifying question instead.\n- No route clearly fits — handle it yourself or ask; never summon on a guess.\n\nWhen one fires, call `{inspect}` with `reveal=\"routes\"` first to see the agents and what\neach is for. Every route carries a claim: its ability (what that agent+model is for)\nand the effort levels it accepts — pick the route whose ability covers the work. Set\n`effort` only when the user explicitly asked for a level; otherwise leave it unset so\nthe agent's own default applies. Then `{summon}` — copying its `agent_id` and `model`\nexactly from that route, and writing a self-contained brief since the other agent may\nnot see this chat. Summon hands over this conversation where it is; to continue\nsomewhere else, `{teleport}` there with `resume=true` and summon from the thread you\nland in. It is refused on a group's main conversation and in native sessions. You\nyourself are not a valid summon target.\n\n### `{teleport}` — relocate yourself\nMove this conversation into a new thread that *you* keep handling, carrying\neverything said so far. Use it for multi-step or long-running work that deserves its\nown thread but that you are the right one to do — no other agent involved.\nOmit `destination` or use null for the current address, or supply another address.\nTeleport requires attached conversation history that the agent can copy, and the\ndestination must support continuing that agent. Shared history can only move into\na sub-thread of the current chat; moving to another address requires a private\nsource. If a requested new thread cannot be created, the move fails without\nsilently continuing at its parent.\n\nTo work on a project, add `project` (from `{inspect}` with `reveal=\"projects\"`), and\n`ref` — a branch, tag or commit — only when the default branch is the wrong place to\nstart: the thread you land in is bound to it and you resume in its workspace, where\nwork is kept. A thread about no project runs in a throwaway tree, so do this before\nyou start work, not after. From inside a thread, use `destination=null`,\n`new_thread=false` and `project` to bind this one without opening another.\nStaying put requires a project; a thread binds once, and a different project is\na different thread. Native sessions must move into a new thread.\n\n### `{scheme}` — take it to the user privately\nContinue one-to-one with the person who asked, in their direct messages: for work that\nis theirs alone, or that does not belong in front of the group. Whoever already handles\ntheir direct messages picks it up, so write `brief` self-contained — it may not be you,\nand they cannot see this chat. `hint` opens the conversation over there and is the\nfirst thing they read; nothing is posted here, so close out your own reply by saying\nthe work is moving, not what it is. Its destination is a target object with\n`kind=\"dm\"`, optionally naming a connected `channel` ID for the user's linked\naccount there. Omitting the destination uses this channel's DM; that local move\nrequires a shared source. Another connected channel's DM can also be selected\nfrom a private source. The tool refuses unsupported or unlinked destinations.\n\n### `{dismiss}` — give the workspace back when the work is done\nA thread about a project keeps its workspace between turns, which is what makes its\nnext turn a resume. When the work is finished for good — merged, delivered, or\ndropped — say so with `{dismiss}`: the tree is released when this turn ends, after\nthis turn's work is saved to the project's mirror, and a later message here forks it\nafresh from there, so nothing is lost but the disk. Not for a pause — a thread\nwaiting on someone keeps its tree, and idle ones are swept on their own. Wrap up in\nthe same turn; your working directory goes with it.\n"
HANDOFF_INSTRUCTION
module-attribute
¶
HANDOFF_INSTRUCTION = "\n\n### Writing a brief\nA `summon` or `scheme` brief is the receiver's whole opening prompt, and it is\nbudgeted. Say, in this order and only where it changes what they do next: the goal\nin the user's own words; the constraints they set; the decisions made and why; what\nis done; what was tried and failed, so it is not tried again; what is left, first\nitem first; and what a finished result looks like. Leave the rest to the ledger:\nthe receiver reads every thread this person has spoken in, so cite a message by\nits `#msg:<id>` handle and name what to search for instead of repeating it. Do\nnot restate the project — the workspace's own instructions carry that.\n"
ACCOMPLICE_INSTRUCTION
module-attribute
¶
ACCOMPLICE_INSTRUCTION = "You are an accomplice: another agent commissioned you, and your reply is\nyour report back to it. There is no user here to ask, and anything that needs a\nhuman approval is declined immediately — work from the brief you were given within\nwhat you can do unaided; if something is under-specified or unapprovable, state the\nassumption or the blocker in your report and proceed.\n"
SEND_INSTRUCTION_TEMPLATE
module-attribute
¶
SEND_INSTRUCTION_TEMPLATE = "\n### `{send}` — deliver something now, without ending your turn\nFor a progress update, an intermediate result, or an image/file you produced along\nthe way. Anything sent this way is already delivered: your final reply continues from\nthere — summarize or extend it, never restate it. If everything worth saying went out\nalready, close with a short wrap-up rather than re-sending it.\n\n`destination` defaults to a target object with `kind=\"here\"`. A target with\n`kind=\"dm\"` delivers to the person who asked, privately —\nfor something that is *for them*, like a summary sent over; say in your reply that you\nsent it. `dm` hands nothing over: you keep this conversation and nobody picks the work\nup there, so use `{scheme}` when the work itself should continue privately. Asking for\n`dm` while already in that person's direct messages is fine — it lands here.\nA DM target can name a connected `channel` ID to reach their linked account there.\nIf no current conversation is attached, that channel must be explicit. Use the\nselected address's `channel_tentacle_id` as `channel`; the recipient is resolved\nfrom the user's linked profile.\n\nTo thread onto a specific message in a busy chat, lead with a reply segment whose id\nis that message's `#msg:<id>` handle — it must be the first segment. To ping someone,\nuse an `at` segment with their user id.\n"
COMMISSION_INSTRUCTION
module-attribute
¶
COMMISSION_INSTRUCTION = "\n### `commission` — put another agent to work in the background (you keep the conversation)\nWhere `summon` hands the conversation away, `commission` does not: another agent works a\nself-contained task and the tool returns its report — the user sees only your reply.\nCall `inspect` with `reveal=\"routes\"` and no `channel` to choose a route from this\nconversation's channel; the same claim and effort rules as `summon` apply.\nGive the accomplice a short mnemonic `name`. The brief must stand alone: the\naccomplice cannot see this chat and has no user to ask, so include the goal, the\nrelevant context, and what a finished result looks like. Several commissions in one\nreply run concurrently.\n\n### `whisper` — a quiet word to an accomplice\nContinue an accomplice's work by `name`: it remembers everything it did. Use it to\nrefine or extend that work instead of commissioning a new accomplice.\n"
Accomplices
dataclass
¶
The accomplices one stretch of a run called and has not heard back from.
Source code in octomate/capabilities/gateway.py
watch
async
¶
stream with each accomplice's start before its call and its finish
before its result. One left unanswered finishes with the stretch: cancelled
with it, or failed.
Source code in octomate/capabilities/gateway.py
observe ¶
Source code in octomate/capabilities/gateway.py
abandon ¶
Source code in octomate/capabilities/gateway.py
GatewayCapability
dataclass
¶
Bases: AbstractCapability[None]
The gateway spells as one turn's toolset: inspect, summon, teleport, scheme, send and dismiss, plus the accomplice spells when there is a thread to run them in.
Source code in octomate/capabilities/gateway.py
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 819 820 821 822 823 | |
commission_timeout
class-attribute
instance-attribute
¶
commissioning
class-attribute
instance-attribute
¶
toolset
class-attribute
instance-attribute
¶
commission_deps ¶
The live handles the accomplice spells run with. Registration only offers the spells when all four are set, so a miss here is a construction bug, not a model mistake.
Source code in octomate/capabilities/gateway.py
run_accomplice
async
¶
run_accomplice(
ctx: RunContext[None],
*,
child: Conversation,
run_name: str,
prompt: str,
model: Model | str | None,
effort: str | None,
) -> str
Source code in octomate/capabilities/gateway.py
inspect
async
¶
inspect(
ctx: RunContext[None],
reveal: InspectFacet,
channel: str | None = None,
inside: str | None = None,
) -> (
list[AgentRoute]
| list[ChannelAddress]
| list[ProjectSummary]
)
Reveal one facet of what this conversation can reach.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reveal
|
InspectFacet
|
|
required |
channel
|
str | None
|
A connected channel ID from an address. For |
None
|
inside
|
str | None
|
For |
None
|
Source code in octomate/capabilities/gateway.py
summon
async
¶
summon(
ctx: RunContext[None],
agent_id: str,
model: str,
hint: str,
reason: str,
brief: Annotated[str, Field(max_length=8000)],
effort: str | None = None,
) -> str
Hand this conversation to another Octomate agent, who takes it over where it is.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agent_id
|
str
|
The target agent, copied exactly from an |
required |
model
|
str
|
That route's model, copied exactly. |
required |
hint
|
str
|
A short, user-facing note announcing the handoff, recorded with it. |
required |
reason
|
str
|
One line on why this agent fits — recorded with the handoff, not shown to the user as the reply. |
required |
brief
|
Annotated[str, Field(max_length=8000)]
|
The self-contained brief the other agent starts from. It becomes
their opening prompt and they cannot see this conversation, so give
the goal, the relevant context and decisions, what's been tried, and
what a finished result looks like. If anything you carry says how to
hand off — a handoff skill, a plugin, your own harness's instructions
— follow it; this is the floor. Reference rather than repeat: the
receiver reads every thread this person has spoken in, so cite a
message by its |
required |
effort
|
str | None
|
How hard the agent should think, from the effort levels the route's claim offers. Set it only when the user explicitly asked for a level; omitted, the agent's own default applies. |
None
|
Source code in octomate/capabilities/gateway.py
teleport
async
¶
teleport(
ctx: RunContext[None],
hint: str,
destination: ChannelAddress | None = None,
project: str | None = None,
ref: str | None = None,
new_thread: bool = True,
resume: bool = False,
) -> str
Continue this conversation yourself somewhere else; everything said so far comes with you. This turn ends on it, and your context is intact there.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hint
|
str
|
The short, user-facing thread-starter message. |
required |
destination
|
ChannelAddress | None
|
The address at which to create a thread; omit for the current chat. The owning channel validates it independently of discovery and requires the requesting user's linked identity. Shared history stays under its current parent. Teleport requires attached history that the agent can copy. |
None
|
new_thread
|
bool
|
Create a thread before continuing. Set false only to bind
the current thread to a project without opening another one.
For creation, the address must have no |
True
|
project
|
str | None
|
A project's name, copied exactly from |
None
|
ref
|
str | None
|
The branch, tag or commit that workspace starts from; omit it for the project's default branch. |
None
|
resume
|
bool
|
Carry on working there at once, as after any tool call. Leave it false when the move is all that was asked: you continue when the user next writes there. |
False
|
Source code in octomate/capabilities/gateway.py
scheme
async
¶
scheme(
ctx: RunContext[None],
hint: str,
brief: Annotated[str, Field(max_length=8000)],
destination: SchemeTarget = DIRECT_TARGET,
) -> str
Continue this with the user one-to-one, in their direct messages.
Whoever already handles that user's direct messages picks the work up — you do not choose them, and their direct messages keep the owner they had. Use it for work that is that person's alone, or that does not belong in front of the group.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hint
|
str
|
The short, user-facing line that opens the conversation over there, where they will read it before anything else. Say that the work is being picked up — not the brief, which is written for whoever answers. |
required |
brief
|
Annotated[str, Field(max_length=8000)]
|
The self-contained brief whoever answers there starts from. They
cannot see this conversation, so give the goal, the relevant context and
decisions, what's been tried, and what a finished result looks like. If
anything you carry says how to hand off — a handoff skill, a plugin,
your own harness's instructions — follow it; this is the floor.
Reference rather than repeat: the receiver reads every thread this
person has spoken in, so cite a message by its |
required |
destination
|
SchemeTarget
|
A target object with |
DIRECT_TARGET
|
Source code in octomate/capabilities/gateway.py
send
async
¶
send(
ctx: RunContext[None],
segments: list[MessageSegment],
destination: SendTarget = HERE_TARGET,
) -> ToolReturn[str]
Deliver these segments immediately, without ending your turn — a progress update, an intermediate result, an image or a file. What you send here is already delivered: do NOT repeat it in your final reply.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
segments
|
list[MessageSegment]
|
What to deliver. |
required |
destination
|
SendTarget
|
A target object with |
HERE_TARGET
|
Source code in octomate/capabilities/gateway.py
dismiss
async
¶
Give this thread's workspace back, now that the work in it is done — merged, delivered, or dropped for good, not paused. It is released when this turn ends, after this turn's work is saved to the project's mirror, so nothing is lost: a later message on this thread forks it afresh from there. Only the disk goes, and it goes now rather than after the idle window the sweep would otherwise wait out.
Source code in octomate/capabilities/gateway.py
commission
async
¶
commission(
ctx: RunContext[None],
name: str,
agent_id: str,
model: str,
brief: str,
effort: str | None = None,
) -> str
Draw another Octomate agent into working a task and get its report back — the user sees none of it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Your name for this accomplice — short and mnemonic, e.g.
|
required |
agent_id
|
str
|
The agent to draw in, copied exactly from an
|
required |
model
|
str
|
That route's model, copied exactly. |
required |
brief
|
str
|
The self-contained work order. The accomplice cannot see this conversation and has no user to ask, so give the goal, the relevant context and decisions, and what a finished result looks like. |
required |
effort
|
str | None
|
How hard the accomplice should think, from the levels the route's claim offers. Set it only when the user explicitly asked for a level. |
None
|
Source code in octomate/capabilities/gateway.py
whisper
async
¶
A quiet word to an accomplice you commissioned. It keeps everything it did — your whisper continues its work, not a fresh start.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name you gave the accomplice when you commissioned it. |
required |
message
|
str
|
The follow-up work order — self-contained, like a brief; the accomplice still cannot see this conversation. |
required |
Source code in octomate/capabilities/gateway.py
get_instructions ¶
wrap_run_event_stream
async
¶
wrap_run_event_stream(
ctx: RunContext[None],
*,
stream: AsyncIterable[AgentStreamEvent],
) -> AsyncIterable[AgentStreamEvent]
Put each send's stashed MessageSentEvent on the run stream,
where the consumer rendering this run delivers it, and each accomplice's
start and finish around its call.
Source code in octomate/capabilities/gateway.py
gateway_instructions ¶
The gateway's routing instruction, each spell rendered by the caller's own tool
naming — the identity for Inkling, gateway_… on the served server — so every
agent reads one contract under the names its runtime lists the tools by.
Source code in octomate/capabilities/gateway.py
octomate.capabilities.ask ¶
Ask capability: the deferred ask-the-user tool.
A capability rather than a loose toolset entry so the caller decides per run whether a user exists to ask at all — an interactive run mounts it, an accomplice run simply never sees the tool.
AskCapability
dataclass
¶
Bases: AbstractCapability[None]
Offers ask_questions — the deferred ask-the-user batch.
Source code in octomate/capabilities/ask.py
toolset
class-attribute
instance-attribute
¶
octomate.capabilities.history ¶
History capability: the thread ledger of the person a run answers.
Thread history is what people, bots and agents visibly said — ThreadMessage rows,
including the ones that did not wake this agent. The model ledger is not offered
here: it exists for audit and for rebuilding a conversation's context, not for a
model to read.
The scope is the person, not the thread: a run reads every thread the human it is
answering has spoken in, on any account the registry links to them — this thread,
their direct messages elsewhere, the chat a handoff came from. So the capability is
user-scoped, bound per run by for_profile, and no tool takes a conversation id.
The tools are methods with docstrings, as the gateway's spells are, so a runtime that reaches them some other way than pydantic-ai projects the same contracts.
HISTORY_INSTRUCTION_TEMPLATE
module-attribute
¶
HISTORY_INSTRUCTION_TEMPLATE = "## Searching history\n\nThread history is what people, bots, and agents visibly said — in this thread, and\nin every other thread the person you are answering has spoken in, on any of their\nlinked accounts. Messages that did not wake you are there too.\n\n- `{search_thread_history}` searches all of it for English terms, requiring every\n term and ranking the best lexical matches first. It accepts an optional actor\n kind. Prefer it when the user refers to what was said, here or elsewhere.\n- `{read_thread_history_before}` and `{read_thread_history_after}` page a thread\n around a search hit, or around a `#msg:<id>` handle a brief cited.\n"
HISTORY_TOOLS
module-attribute
¶
HISTORY_TOOLS: tuple[str, ...] = (
"search_thread_history",
"read_thread_history_before",
"read_thread_history_after",
)
HISTORY_INSTRUCTIONS
module-attribute
¶
HistoryCapability
dataclass
¶
Bases: AbstractCapability[Any]
Thread-history tools over the history of the person a run answers.
Mounted user-scoped: the tentacle asks for_profile for the copy bound to
that run's user and mounts that, so no tool has to ask who is asking.
Source code in octomate/capabilities/history.py
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 | |
toolset
class-attribute
instance-attribute
¶
for_profile
async
¶
This capability bound to one person. Everyone gets one: a visitor's history is the threads their one account has spoken in.
Source code in octomate/capabilities/history.py
anchor
async
¶
The message a paging tool is anchored on, with a refusal spoken as a retry: a handle outside this person's history is not theirs to page.
Source code in octomate/capabilities/history.py
search_thread_history
async
¶
search_thread_history(
query: str,
actor_kind: ChannelActorKind | None = None,
limit: int = 10,
) -> list[ThreadMessage]
Find visible thread messages matching every English term in query,
with related word forms matched by stemming, across every thread the person
you are answering has spoken in — this one, their direct messages, other
chats, on any of their linked accounts. Optionally restrict to an actor kind
such as "human", "agent", "bot", or "system". Best lexical match first,
newest first when scores tie. Use a hit's id to page its local context.
Source code in octomate/capabilities/history.py
read_thread_history_before
async
¶
The visible thread messages immediately preceding message_id in its
thread, oldest first. message_id is a row id or a #msg:<id> handle, as
a search hit or a brief shows it; a message outside this person's history
is refused.
Source code in octomate/capabilities/history.py
read_thread_history_after
async
¶
The visible thread messages immediately following message_id in its
thread, oldest first. message_id is a row id or a #msg:<id> handle, as
a search hit or a brief shows it; a message outside this person's history
is refused.
Source code in octomate/capabilities/history.py
get_toolset ¶
history_instructions ¶
The history instruction, each tool rendered by the caller's own naming.
Source code in octomate/capabilities/history.py
conversation_id ¶
Source code in octomate/capabilities/history.py
thread_id
async
¶
Source code in octomate/capabilities/history.py
octomate.capabilities.todos ¶
Todo capability: a persisted, conversation-scoped task list the agent manages.
Models the pydantic-ai-todo toolset (read/write/add/update/remove + subtasks &
dependencies) but persists via Arcanus (TodoManager) and emits granular todo
events onto the octomate event stream instead of a private callback bus. The
capability holds the manager; each tool reads ctx.conversation_id for scoping,
so the agent's deps type is untouched. Tools stash the events they produce on
ToolReturn.metadata; wrap_run_event_stream forwards them onto the stream.
TODO_TOOL_NAMES
module-attribute
¶
TODO_TOOL_NAMES = frozenset(
{
"read_todos",
"write_todos",
"add_todo",
"update_todo_status",
"remove_todo",
"add_subtask",
"set_dependency",
"get_available_tasks",
}
)
TODO_INSTRUCTION
module-attribute
¶
TODO_INSTRUCTION = '## Task management\n\nYou have todo tools to plan and track multi-step work:\n- `read_todos` — view the current tasks with their ids and statuses.\n- `write_todos` — replace the whole list (use to lay out or restructure a plan).\n- `add_todo` — append one task without disturbing the rest.\n- `update_todo_status` — move a task between pending / in_progress / completed.\n- `remove_todo` — delete a task by id.\n- `add_subtask` / `set_dependency` / `get_available_tasks` — break tasks down,\n order them, and find what is unblocked.\n\nUse them for non-trivial work (3+ steps). Keep exactly one task `in_progress`,\nmark tasks `completed` immediately when done, and pass each task an imperative\n`content` ("Run tests") plus a present-continuous `active_form` ("Running tests").\n'
TodoItem
pydantic-model
¶
Bases: BaseModel
One todo in a write_todos call (the model rewrites the whole list).
Fields:
-
content(str) -
status(TodoStatus) -
active_form(str) -
id(str | None) -
parent_id(str | None) -
depends_on(list[str])
Source code in octomate/capabilities/todos.py
active_form
pydantic-field
¶
Present-continuous label, e.g. 'Fixing the login bug'.
TodoCapability
dataclass
¶
Bases: AbstractCapability[Any]
Adds the persisted todo toolset + its instruction, and emits granular todo events onto the stream as the model mutates the list.
Source code in octomate/capabilities/todos.py
manager
class-attribute
instance-attribute
¶
toolset
class-attribute
instance-attribute
¶
get_toolset ¶
get_instructions ¶
wrap_run_event_stream
async
¶
wrap_run_event_stream(
ctx: RunContext[Any],
*,
stream: AsyncIterable[AgentStreamEvent],
) -> AsyncIterable[AgentStreamEvent]
Source code in octomate/capabilities/todos.py
by_ref ¶
is_blocked ¶
has_cycle ¶
Would making todo_ref depend on depends_on_ref create a cycle?
Source code in octomate/capabilities/todos.py
available_tasks ¶
render_todos ¶
Source code in octomate/capabilities/todos.py
update_events ¶
Source code in octomate/capabilities/todos.py
build_todo_toolset ¶
Source code in octomate/capabilities/todos.py
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 | |
octomate.capabilities.mcp ¶
Octomate's MCP tentacles as an Inkling capability: the tools the served server lists a turn, mounted in process for one run.
Inkling runs in this process, so nothing goes over the wire — not even in memory: the tentacles' server is called as the object it is, no client and no transport between them. The run's Octomate session is closed over, every tentacle lists and calls as the person that session is for, and the linking pair is there beside them. The spells and the history Inkling has as capabilities of its own, so what it mounts here is the tentacles' server alone, deferred: named by a catalog line, its tools flagged for the model to discover or load before they are on the wire, so a listing that differs per person never touches the prompt prefix.
TentaclesToolset ¶
Bases: AbstractToolset[None]
A FastMCP server's tools as a pydantic-ai toolset, the server called in process as the object it is. Named as the server is, and its instructions are the server's own.
Source code in octomate/capabilities/mcp.py
get_instructions
async
¶
get_tools
async
¶
Source code in octomate/capabilities/mcp.py
call_tool
async
¶
call_tool(
name: str,
tool_args: dict[str, Any],
ctx: RunContext[None],
tool: ToolsetTool[None],
) -> dict[str, Any] | str
Source code in octomate/capabilities/mcp.py
tentacles_capability ¶
The capability a run mounts: the tentacles' server built over session,
deferred behind a catalog line naming what it holds.
Source code in octomate/capabilities/mcp.py
octomate.capabilities.tools ¶
Tool-level capabilities: reporting a failed tool call to the model instead of ending the turn.
ToolFailureCapability
dataclass
¶
Bases: AbstractCapability[None]
Report a failed tool to the model instead of ending the turn.
Pydantic AI ends a run when a tool raises anything but its own control-flow signals, so one broken call — a bad argument, a provider that blinked, a spell used out of order — costs the whole answer: the stream stops mid-way, whatever had already been drawn is abandoned, and the person is left with a trace id instead of a reply. The model is the one that can work around a failed call, and the one that can say what went wrong, so it is told.
ToolFailed rather than ModelRetry: a call that failed for reasons the
model did not cause is not a call to make again, and a retry spends a budget
that ends the run when it runs out — which is the same lost turn, one attempt
later. A failure it can read leaves it free to try another way or explain.
Runs stay bounded by Inkling's configured UsageLimits.request_limit, not by
how many times one tool may break.
Source code in octomate/capabilities/tools.py
on_tool_execute_error
async
¶
on_tool_execute_error(
ctx: RunContext[None],
*,
call: ToolCallPart,
tool_def: ToolDefinition,
args: ValidatedToolArgs,
error: Exception,
) -> Never