Skip to content

Agents

octomate.config.agents.common

Shared agent configuration and routing metadata.

AgentRouteModelName

AgentRouteModelName = Annotated[str, Field(min_length=1)]

Claim dataclass

What one route advertises: its ability and the effort levels it accepts.

Source code in octomate/config/agents/common.py
@dataclass(frozen=True)
class Claim:
    """What one route advertises: its ability and the effort levels it accepts."""

    # What this route is for — per-route, not per-agent, so two models of one
    # agent can advertise differently.
    ability: str
    # Native effort names accepted by this route, in the harness's advertised order.
    efforts: tuple[str, ...] = ()
    # The level a run gets when its caller names none; None when the runtime
    # decides and does not say which.
    default_effort: str | None = None

    def __str__(self) -> str:
        return f"[effort {'/'.join(self.efforts) or 'default'}] {self.ability}"

ability instance-attribute

ability: str

efforts class-attribute instance-attribute

efforts: tuple[str, ...] = ()

default_effort class-attribute instance-attribute

default_effort: str | None = None

AgentConfig pydantic-model

Bases: BaseModel

What every agent tentacle's config block declares, whichever runtime it drives: the agent reads the same way everywhere — declared and enabled, or absent — and carries its own half of the gateway switch.

Fields:

Source code in octomate/config/agents/common.py
class AgentConfig(BaseModel):
    """What every agent tentacle's config block declares, whichever runtime it
    drives: the agent reads the same way everywhere — declared and enabled, or
    absent — and carries its own half of the gateway switch."""

    type: str

    enabled: bool = Field(
        default=True,
        description="Whether to register the tentacle when its config block exists.",
    )
    gateway: bool = Field(
        default=True,
        description="Whether this agent's driven turns offer the gateway spells — "
        "routing, and binding a thread to a project. Off, no channel connection can "
        "switch them on for it.",
    )

type pydantic-field

type: str

enabled pydantic-field

enabled: bool = True

Whether to register the tentacle when its config block exists.

gateway pydantic-field

gateway: bool = True

Whether this agent's driven turns offer the gateway spells — routing, and binding a thread to a project. Off, no channel connection can switch them on for it.

octomate.config.agents.claude

Claude Code configuration.

logger module-attribute

logger = logging.getLogger(__name__)

ClaudeSSHConfig pydantic-model

Bases: BaseModel

Remote-host settings for the Claude tentacle.

The SSH transport is parked: nothing constructs it, so a block here is warned about and not honoured — the run stays local. See ClaudeCodeConfig.ssh.

Fields:

Source code in octomate/config/agents/claude.py
class ClaudeSSHConfig(BaseModel):
    """Remote-host settings for the Claude tentacle.

    The SSH transport is parked: nothing constructs it, so a block here is warned
    about and not honoured — the run stays local. See `ClaudeCodeConfig.ssh`.
    """

    host: str
    identity_file: str | None = None
    ssh_options: list[str] = Field(default_factory=list)
    claude_bin: str = "claude"

host pydantic-field

host: str

identity_file pydantic-field

identity_file: str | None = None

ssh_options pydantic-field

ssh_options: list[str]

claude_bin pydantic-field

claude_bin: str = 'claude'

ClaudeCodeConfig pydantic-model

Bases: AgentConfig

Claude Agent SDK runner, selected by type: claude.

Opt-in: the agent is absent unless a block is supplied. The CLI supplies the model catalog. claude runs as a local subprocess; an ssh block is warned about and not honoured while the remote transport is parked.

Config:

  • extra: ignore

Fields:

Validators:

Source code in octomate/config/agents/claude.py
class ClaudeCodeConfig(AgentConfig):
    """Claude Agent SDK runner, selected by `type: claude`.

    Opt-in: the agent is absent unless a block is supplied. The CLI supplies the
    model catalog. `claude` runs as a local subprocess; an `ssh` block is warned
    about and not honoured while the remote transport is parked.
    """

    model_config = ConfigDict(extra="ignore")

    type: Literal["claude"] = "claude"

    enabled: bool = Field(
        default=True,
        description="Whether to register the Claude tentacle when the config block exists.",
    )
    instrument: bool = Field(
        default=False,
        description="Export native Claude OTLP spans to Octomate's Logfire project "
        "under the driving trace.",
    )
    claims: dict[str, Claim] = Field(
        default_factory=dict,
        description="Metadata for models whose harness omits descriptions or effort "
        "capabilities. Keys are provider-qualified model names.",
    )
    permission_mode: ClaudePermissionMode = Field(
        default="default",
        description=(
            "Approval posture for a Claude conversation whose thread is in no "
            "project, or whose project declares none. Handed to the SDK verbatim."
        ),
    )
    max_turns: int | None = None
    ssh: ClaudeSSHConfig | None = Field(
        default=None,
        description=(
            "Remote host to run `claude` on. Disabled, and no longer wired: a "
            "run happens in its thread's workspace, and there is nothing that "
            "makes a workspace on another machine. The tentacle hands the SDK "
            "no transport at all now; `SSHTransport` is kept as it stands, but "
            "nothing constructs it. Re-enabling is three things rather than "
            "one — somewhere remote to fork a workspace into, a directory on "
            "`ClaudeSSHConfig` to name it, and the transport wired back in. "
            "Setting it is warned about rather than refused: with the transport "
            "parked the block reaches nothing, so failing a start over it would "
            "cost more than it saves."
        ),
    )
    approval_timeout: float | None = Field(
        default=3600.0,
        description=(
            "Seconds to wait for a human approval/answer before the card expires "
            "and the pending tool is denied (so the live run unblocks). An hour by "
            "default, because not answering is the ordinary case rather than the "
            "exotic one, and an unbounded wait leaves the thread unusable for good. "
            "None waits indefinitely."
        ),
    )

    @field_validator("ssh")
    @classmethod
    def warn_remote_runs_are_off(
        cls, ssh: ClaudeSSHConfig | None
    ) -> ClaudeSSHConfig | None:
        """Say that a configured remote host is not honoured, and keep it as written.

        This refused the whole config while the tentacle still built an SSH
        transport, since a block that would have been obeyed had to be stopped
        loudly. The transport is parked now and the block reaches nothing, so the
        value is left as the operator wrote it and only the effect is reported.
        """
        if ssh is not None:
            logger.warning(
                "tentacles.claude.ssh is not honoured and the run stays local: a run "
                "happens in its thread's workspace, and nothing makes one on %s",
                ssh.host,
            )
        return ssh

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='ignore')

type pydantic-field

type: Literal['claude'] = 'claude'

enabled pydantic-field

enabled: bool = True

Whether to register the Claude tentacle when the config block exists.

instrument pydantic-field

instrument: bool = False

Export native Claude OTLP spans to Octomate's Logfire project under the driving trace.

claims pydantic-field

claims: dict[str, Claim]

Metadata for models whose harness omits descriptions or effort capabilities. Keys are provider-qualified model names.

permission_mode pydantic-field

permission_mode: ClaudePermissionMode = 'default'

Approval posture for a Claude conversation whose thread is in no project, or whose project declares none. Handed to the SDK verbatim.

max_turns pydantic-field

max_turns: int | None = None

ssh pydantic-field

ssh: ClaudeSSHConfig | None = None

Remote host to run claude on. Disabled, and no longer wired: a run happens in its thread's workspace, and there is nothing that makes a workspace on another machine. The tentacle hands the SDK no transport at all now; SSHTransport is kept as it stands, but nothing constructs it. Re-enabling is three things rather than one — somewhere remote to fork a workspace into, a directory on ClaudeSSHConfig to name it, and the transport wired back in. Setting it is warned about rather than refused: with the transport parked the block reaches nothing, so failing a start over it would cost more than it saves.

approval_timeout pydantic-field

approval_timeout: float | None = 3600.0

Seconds to wait for a human approval/answer before the card expires and the pending tool is denied (so the live run unblocks). An hour by default, because not answering is the ordinary case rather than the exotic one, and an unbounded wait leaves the thread unusable for good. None waits indefinitely.

warn_remote_runs_are_off pydantic-validator

warn_remote_runs_are_off(
    ssh: ClaudeSSHConfig | None,
) -> ClaudeSSHConfig | None

Say that a configured remote host is not honoured, and keep it as written.

This refused the whole config while the tentacle still built an SSH transport, since a block that would have been obeyed had to be stopped loudly. The transport is parked now and the block reaches nothing, so the value is left as the operator wrote it and only the effect is reported.

Source code in octomate/config/agents/claude.py
@field_validator("ssh")
@classmethod
def warn_remote_runs_are_off(
    cls, ssh: ClaudeSSHConfig | None
) -> ClaudeSSHConfig | None:
    """Say that a configured remote host is not honoured, and keep it as written.

    This refused the whole config while the tentacle still built an SSH
    transport, since a block that would have been obeyed had to be stopped
    loudly. The transport is parked now and the block reaches nothing, so the
    value is left as the operator wrote it and only the effect is reported.
    """
    if ssh is not None:
        logger.warning(
            "tentacles.claude.ssh is not honoured and the run stays local: a run "
            "happens in its thread's workspace, and nothing makes one on %s",
            ssh.host,
        )
    return ssh

octomate.config.agents.codex

Codex configuration and SDK option types.

CodexPersonality

CodexPersonality = Literal['none', 'friendly', 'pragmatic']

CodexReasoningSummary

CodexReasoningSummary = Literal[
    "auto", "concise", "detailed", "none"
]

CodexConfig pydantic-model

Bases: AgentConfig

OpenAI Codex SDK runner, selected by type: codex.

Opt-in: the agent is absent unless a tentacles.<id> block with type: codex is supplied. The app-server supplies the model catalog. Most fields below are default arguments for Codex SDK calls; the tentacle may compose them with per-run overrides before calling the SDK.

Config:

  • extra: ignore

Fields:

Validators:

Source code in octomate/config/agents/codex.py
class CodexConfig(AgentConfig):
    """OpenAI Codex SDK runner, selected by `type: codex`.

    Opt-in: the agent is absent unless a `tentacles.<id>` block with `type: codex`
    is supplied. The app-server supplies the model catalog. Most fields below
    are default arguments for Codex SDK calls; the tentacle may compose them with
    per-run overrides before calling the SDK.
    """

    model_config = ConfigDict(extra="ignore")

    type: Literal["codex"] = "codex"

    enabled: bool = Field(
        default=True,
        description="Whether to register the Codex tentacle when the config block exists.",
    )
    instrument: bool = Field(
        default=False,
        description="Export native Codex OTLP spans to Octomate's Logfire project "
        "under the driving trace.",
    )
    runtime: CodexSdkConfig = Field(
        default_factory=CodexSdkConfig,
        description=(
            "Default SDK runtime config used to launch the local Codex app-server."
        ),
    )
    claims: dict[str, Claim] = Field(
        default_factory=dict,
        description="Metadata for models whose harness omits descriptions or effort "
        "capabilities. Keys are provider-qualified model names.",
    )
    permission_mode: CodexPermissionMode = Field(
        default="user_review",
        description=(
            "Codex permission preset: user_review (Ask for approval), auto_review "
            "(Approve for me), or full_access (Full access). The first two restrict "
            "writes to the workspace and review network access; full_access removes "
            "the sandbox and approval prompts."
        ),
    )
    base_instructions: str | None = Field(
        default=None,
        description="Default base instructions for new Codex threads.",
    )
    developer_instructions: str | None = Field(
        default=None,
        description=(
            "Default developer instructions for starting or resuming Codex threads."
        ),
    )
    ephemeral: bool | None = Field(
        default=None,
        description="Default ephemeral flag for newly started Codex threads.",
    )
    personality: CodexPersonality | None = Field(
        default=None,
        description="Default Codex personality preset for threads and turns.",
    )
    effort: CodexReasoningEffort | None = Field(
        default=None,
        description="Default reasoning effort override for Codex turns.",
    )
    summary: CodexReasoningSummary | None = Field(
        default=None,
        description="Default reasoning summary setting for Codex turns.",
    )
    approval_timeout: float | None = Field(
        default=3600.0,
        description=(
            "Seconds to wait for a human Codex approval/answer before the card "
            "expires and the SDK request is denied. An hour by default, because not "
            "answering is the ordinary case rather than the exotic one, and an "
            "unbounded wait leaves the thread unusable for good. None waits "
            "indefinitely."
        ),
    )

    @model_validator(mode="before")
    @classmethod
    def reject_separate_sandbox[T](cls, value: T) -> T:
        if isinstance(value, dict) and "sandbox" in value:
            raise ValueError(
                "Codex sandbox is now part of permission_mode; remove sandbox and "
                "choose user_review, auto_review, or full_access"
            )
        return value

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='ignore')

type pydantic-field

type: Literal['codex'] = 'codex'

enabled pydantic-field

enabled: bool = True

Whether to register the Codex tentacle when the config block exists.

instrument pydantic-field

instrument: bool = False

Export native Codex OTLP spans to Octomate's Logfire project under the driving trace.

runtime pydantic-field

runtime: CodexConfig

Default SDK runtime config used to launch the local Codex app-server.

claims pydantic-field

claims: dict[str, Claim]

Metadata for models whose harness omits descriptions or effort capabilities. Keys are provider-qualified model names.

permission_mode pydantic-field

permission_mode: CodexPermissionMode = 'user_review'

Codex permission preset: user_review (Ask for approval), auto_review (Approve for me), or full_access (Full access). The first two restrict writes to the workspace and review network access; full_access removes the sandbox and approval prompts.

base_instructions pydantic-field

base_instructions: str | None = None

Default base instructions for new Codex threads.

developer_instructions pydantic-field

developer_instructions: str | None = None

Default developer instructions for starting or resuming Codex threads.

ephemeral pydantic-field

ephemeral: bool | None = None

Default ephemeral flag for newly started Codex threads.

personality pydantic-field

personality: CodexPersonality | None = None

Default Codex personality preset for threads and turns.

effort pydantic-field

effort: ReasoningEffort | None = None

Default reasoning effort override for Codex turns.

summary pydantic-field

summary: CodexReasoningSummary | None = None

Default reasoning summary setting for Codex turns.

approval_timeout pydantic-field

approval_timeout: float | None = 3600.0

Seconds to wait for a human Codex approval/answer before the card expires and the SDK request is denied. An hour by default, because not answering is the ordinary case rather than the exotic one, and an unbounded wait leaves the thread unusable for good. None waits indefinitely.

reject_separate_sandbox pydantic-validator

reject_separate_sandbox(value: T) -> T
Source code in octomate/config/agents/codex.py
@model_validator(mode="before")
@classmethod
def reject_separate_sandbox[T](cls, value: T) -> T:
    if isinstance(value, dict) and "sandbox" in value:
        raise ValueError(
            "Codex sandbox is now part of permission_mode; remove sandbox and "
            "choose user_review, auto_review, or full_access"
        )
    return value

octomate.config.agents.deepseek

DeepSeek Harness configuration.

ConfigPath

ConfigPath = Annotated[Path, AfterValidator(expanduser)]

DeepseekConfig pydantic-model

Bases: AgentConfig

WIP DeepSeek Harness runner, registered as the deepseek agent tentacle.

The tentacle owns a dsh web child with a private configuration home, sharing settings and session data with the configured native home. It drives the harness over the /api gateway — HTTP for unary calls, the mux WebSocket for events — the same integration surface dsh's own web client uses. Sessions are per-conversation: the dsh session id is stored as the conversation external_id and prompted again for later turns.

Config:

  • extra: ignore

Fields:

Validators:

Source code in octomate/config/agents/deepseek.py
class DeepseekConfig(AgentConfig):
    """WIP DeepSeek Harness runner, registered as the `deepseek` agent tentacle.

    The tentacle owns a `dsh web` child with a private configuration home,
    sharing settings and session data with the configured native home.
    It drives the harness over the `/api` gateway — HTTP for unary calls,
    the mux WebSocket for events — the
    same integration surface dsh's own web client uses. Sessions are
    per-conversation: the dsh session id is stored as the conversation
    `external_id` and prompted again for later turns.
    """

    model_config = ConfigDict(extra="ignore")

    type: Literal["deepseek"] = "deepseek"

    instrument: bool = Field(
        default=False,
        description="Record DeepSeek session events under the driving Logfire trace.",
    )
    host: Literal["127.0.0.1", "localhost"] = Field(
        default="127.0.0.1",
        description=(
            "Where a dsh serves `/api` — loopback only, enforced here: the "
            "gateway uses HTTP, and a started child binds loopback, "
            "so a remote host could neither be trusted nor answered."
        ),
    )
    port: int = Field(
        default=3081,
        ge=1,
        le=65535,
        description=(
            "Port for Octomate's own DSH child, separate from native DSH's 3080. "
            "An occupied port fails startup; existing runtimes are never attached."
        ),
    )
    browser_url: HttpUrl | None = Field(
        default=None,
        description=(
            "Browser origin of a reverse proxy, e.g. https://dsh.example:8443. "
            "A started child trusts this authority and prints its login link "
            "to the console once. Without this setting the printed link uses "
            "the child's loopback URL. "
            "Configure the proxy separately; the child still binds loopback."
        ),
    )

    executable: str = Field(
        default="dsh",
        description=(
            "The dsh command to spawn `dsh web` — a name resolved on PATH or "
            "an absolute path to a built dsh."
        ),
    )
    extra_args: list[str] = Field(
        default_factory=list,
        description=(
            "Extra arguments placed after `web` and before the web app's "
            "`--host`, `--port`, and `--no-open` flags, e.g. a `--patch` "
            "overlay managed by Octomate. A dsh that "
            "refuses one of these exits and fails the start — only octomate's "
            "own `--no-open` is dropped and retried."
        ),
    )
    dsh_home: ConfigPath = Field(
        default=Path("~/.dsh"),
        # The default rides through ConfigPath's expanduser like any set value.
        validate_default=True,
        description=(
            "Native DSH home whose settings.yaml, .credentials.yaml, sessions "
            "and attachments are shared with Octomate's child. Its plugins, "
            "hooks, MCPs and profile patches are not loaded. The child receives "
            "a separate temporary DSH_HOME."
        ),
    )
    claims: dict[str, Claim] = Field(
        default_factory=dict,
        description="Metadata for models whose harness omits descriptions or effort "
        "capabilities. Keys are provider-qualified model names.",
    )
    permission_mode: DeepseekPermissionMode = Field(
        default="workspace-write",
        description=(
            "Permission preset a dsh conversation falls back to when it carries "
            "none of its own. dsh's preset bundles sandbox mode and approval "
            "policy; switched per session via the `/permission` command."
        ),
    )
    agent_preset: str | None = Field(
        default=None,
        description=(
            "Agent preset new sessions are composed from (`session/create`'s "
            "agentPreset). Null takes the deployment's default preset."
        ),
    )
    approval_timeout: float | None = Field(
        default=3600.0,
        description=(
            "Seconds to wait for a human approval/answer before the card expires "
            "and the dsh request is answered `cancelled` (so the turn unblocks). An "
            "hour by default, because not answering is the ordinary case rather than "
            "the exotic one, and an unbounded wait leaves the thread unusable for "
            "good. None waits indefinitely."
        ),
    )
    ready_timeout: float = Field(
        default=60.0,
        gt=0,
        description=(
            "Seconds to wait for the `dsh web:` readiness banner before the "
            "spawn is declared failed."
        ),
    )

    @field_validator("browser_url")
    @classmethod
    def browser_origin(cls, value: HttpUrl | None) -> HttpUrl | None:
        if value is not None and (
            value.username is not None
            or value.password is not None
            or value.path not in {None, "/"}
            or value.query is not None
            or value.fragment is not None
        ):
            raise ValueError(
                "browser_url must be an origin without credentials, path, query, or fragment"
            )
        return value

model_config class-attribute instance-attribute

model_config = ConfigDict(extra='ignore')

type pydantic-field

type: Literal['deepseek'] = 'deepseek'

instrument pydantic-field

instrument: bool = False

Record DeepSeek session events under the driving Logfire trace.

host pydantic-field

host: Literal['127.0.0.1', 'localhost'] = '127.0.0.1'

Where a dsh serves /api — loopback only, enforced here: the gateway uses HTTP, and a started child binds loopback, so a remote host could neither be trusted nor answered.

port pydantic-field

port: int = 3081

Port for Octomate's own DSH child, separate from native DSH's 3080. An occupied port fails startup; existing runtimes are never attached.

browser_url pydantic-field

browser_url: HttpUrl | None = None

Browser origin of a reverse proxy, e.g. https://dsh.example:8443. A started child trusts this authority and prints its login link to the console once. Without this setting the printed link uses the child's loopback URL. Configure the proxy separately; the child still binds loopback.

executable pydantic-field

executable: str = 'dsh'

The dsh command to spawn dsh web — a name resolved on PATH or an absolute path to a built dsh.

extra_args pydantic-field

extra_args: list[str]

Extra arguments placed after web and before the web app's --host, --port, and --no-open flags, e.g. a --patch overlay managed by Octomate. A dsh that refuses one of these exits and fails the start — only octomate's own --no-open is dropped and retried.

dsh_home pydantic-field

dsh_home: ConfigPath = Path('~/.dsh')

Native DSH home whose settings.yaml, .credentials.yaml, sessions and attachments are shared with Octomate's child. Its plugins, hooks, MCPs and profile patches are not loaded. The child receives a separate temporary DSH_HOME.

claims pydantic-field

claims: dict[str, Claim]

Metadata for models whose harness omits descriptions or effort capabilities. Keys are provider-qualified model names.

permission_mode pydantic-field

permission_mode: DeepseekPermissionMode = 'workspace-write'

Permission preset a dsh conversation falls back to when it carries none of its own. dsh's preset bundles sandbox mode and approval policy; switched per session via the /permission command.

agent_preset pydantic-field

agent_preset: str | None = None

Agent preset new sessions are composed from (session/create's agentPreset). Null takes the deployment's default preset.

approval_timeout pydantic-field

approval_timeout: float | None = 3600.0

Seconds to wait for a human approval/answer before the card expires and the dsh request is answered cancelled (so the turn unblocks). An hour by default, because not answering is the ordinary case rather than the exotic one, and an unbounded wait leaves the thread unusable for good. None waits indefinitely.

ready_timeout pydantic-field

ready_timeout: float = 60.0

Seconds to wait for the dsh web: readiness banner before the spawn is declared failed.

browser_origin pydantic-validator

browser_origin(value: HttpUrl | None) -> HttpUrl | None
Source code in octomate/config/agents/deepseek.py
@field_validator("browser_url")
@classmethod
def browser_origin(cls, value: HttpUrl | None) -> HttpUrl | None:
    if value is not None and (
        value.username is not None
        or value.password is not None
        or value.path not in {None, "/"}
        or value.query is not None
        or value.fragment is not None
    ):
        raise ValueError(
            "browser_url must be an origin without credentials, path, query, or fragment"
        )
    return value

octomate.config.agents.inkling

Inkling configuration and tool output policies.

ToolOutputAction

ToolOutputAction = Annotated[
    TruncateAction | SpillAction | SummarizeAction,
    Field(discriminator="kind"),
]

TruncateAction pydantic-model

Bases: BaseModel

Clamp the return to a character budget. Lossy, and costs nothing.

Fields:

Source code in octomate/config/agents/inkling.py
class TruncateAction(BaseModel):
    """Clamp the return to a character budget. Lossy, and costs nothing."""

    kind: Literal["truncate"] = "truncate"
    max_chars: int = Field(
        default=4_000,
        gt=0,
        description="Characters kept. Counted in characters even when the bands "
        "measure in tokens, since truncation is a character operation.",
    )
    strategy: TruncationStrategy = Field(
        default=TruncationStrategy.head_tail,
        description="Which end survives: `head`, `tail`, or `head_tail` (both, with "
        "the middle elided). `tail` suits a command's output, where the outcome is "
        "at the end; `head` suits a long listing.",
    )

kind pydantic-field

kind: Literal['truncate'] = 'truncate'

max_chars pydantic-field

max_chars: int = 4000

Characters kept. Counted in characters even when the bands measure in tokens, since truncation is a character operation.

strategy pydantic-field

strategy: TruncationStrategy = TruncationStrategy.head_tail

Which end survives: head, tail, or head_tail (both, with the middle elided). tail suits a command's output, where the outcome is at the end; head suits a long listing.

SpillAction pydantic-model

Bases: BaseModel

Store the return whole and hand the model a handle to it. Lossless.

Fields:

Source code in octomate/config/agents/inkling.py
class SpillAction(BaseModel):
    """Store the return whole and hand the model a handle to it. Lossless."""

    kind: Literal["spill"] = "spill"
    preview_chars: int = Field(
        default=1_000,
        gt=0,
        description="Characters shown inline beside the handle, so the model can "
        "judge whether reading the rest back is worth a tool call.",
    )

kind pydantic-field

kind: Literal['spill'] = 'spill'

preview_chars pydantic-field

preview_chars: int = 1000

Characters shown inline beside the handle, so the model can judge whether reading the rest back is worth a tool call.

SummarizeAction pydantic-model

Bases: BaseModel

Replace the return with an LLM summary. Lossy, and costs a model call.

The summary is written by the run's own model. There is deliberately no model override: a name given here would be resolved by pydantic-ai rather than the provider registry that builds every other model this agent runs on, so the two would disagree about what the same name means.

Fields:

  • kind (Literal['summarize'])
Source code in octomate/config/agents/inkling.py
class SummarizeAction(BaseModel):
    """Replace the return with an LLM summary. Lossy, and costs a model call.

    The summary is written by the run's own model. There is deliberately no model
    override: a name given here would be resolved by pydantic-ai rather than the
    provider registry that builds every other model this agent runs on, so the two
    would disagree about what the same name means.
    """

    kind: Literal["summarize"] = "summarize"

kind pydantic-field

kind: Literal['summarize'] = 'summarize'

ToolOutputBand pydantic-model

Bases: BaseModel

A size threshold, and what a return reaching it is replaced with.

Fields:

Source code in octomate/config/agents/inkling.py
class ToolOutputBand(BaseModel):
    """A size threshold, and what a return reaching it is replaced with."""

    over: int = Field(
        gt=0,
        description="Size a return must reach for this band to claim it, in "
        "characters (or tokens, when `over_tokens` is set).",
    )
    action: ToolOutputAction

over pydantic-field

over: int

Size a return must reach for this band to claim it, in characters (or tokens, when over_tokens is set).

action pydantic-field

action: ToolOutputAction

ToolOutputConfig pydantic-model

Bases: BaseModel

What becomes of a tool return too large to sit in the context window.

A tool return persists in history, so an oversized one is re-sent on every later request for the rest of the conversation. Each return is measured once and the largest band whose over it reaches claims it; anything under every threshold is left alone, and bands may be listed in any order. A band that cannot run falls back to truncation, so the payload is bounded either way.

Fields:

Source code in octomate/config/agents/inkling.py
class ToolOutputConfig(BaseModel):
    """What becomes of a tool return too large to sit in the context window.

    A tool return persists in history, so an oversized one is re-sent on every later
    request for the rest of the conversation. Each return is measured once and the
    **largest** band whose `over` it reaches claims it; anything under every threshold
    is left alone, and bands may be listed in any order. A band that cannot run falls
    back to truncation, so the payload is bounded either way.
    """

    enabled: bool = Field(
        default=True,
        description="Whether oversized tool returns are reduced at all. Turning this "
        "off sends every return to the model whole, which is a debugging posture "
        "rather than a deployment one.",
    )
    bands: list[ToolOutputBand] = Field(
        default_factory=lambda: [
            ToolOutputBand(over=10_000, action=SpillAction()),
            ToolOutputBand(over=100_000, action=SummarizeAction()),
        ],
        min_length=1,
        description="Size thresholds and their actions. The default spills past ~10k, "
        "keeping the payload readable through `read_tool_result`, and summarizes past "
        "~100k, where a handle buys the model little — reading a payload that size "
        "back costs more calls than the summary it would have to reconstruct. To "
        "reduce nothing, set `enabled: false` rather than emptying this.",
    )
    over_tokens: bool = Field(
        default=False,
        description="Measure band thresholds in estimated tokens instead of "
        "characters, via a ~4-chars-per-token heuristic.",
    )
    retention_hours: float | None = Field(
        default=6.0,
        gt=0,
        description="How long a spilled payload stays readable before it is pruned. "
        "A spill outlives its run on purpose, since the handle may be read back turns "
        "later, but this host is long-lived and the table would only grow. Null keeps "
        "payloads forever.",
    )

    @property
    def retention(self) -> timedelta | None:
        return (
            timedelta(hours=self.retention_hours)
            if self.retention_hours is not None
            else None
        )

enabled pydantic-field

enabled: bool = True

Whether oversized tool returns are reduced at all. Turning this off sends every return to the model whole, which is a debugging posture rather than a deployment one.

bands pydantic-field

bands: list[ToolOutputBand]

Size thresholds and their actions. The default spills past ~10k, keeping the payload readable through read_tool_result, and summarizes past ~100k, where a handle buys the model little — reading a payload that size back costs more calls than the summary it would have to reconstruct. To reduce nothing, set enabled: false rather than emptying this.

over_tokens pydantic-field

over_tokens: bool = False

Measure band thresholds in estimated tokens instead of characters, via a ~4-chars-per-token heuristic.

retention_hours pydantic-field

retention_hours: float | None = 6.0

How long a spilled payload stays readable before it is pruned. A spill outlives its run on purpose, since the handle may be read back turns later, but this host is long-lived and the table would only grow. Null keeps payloads forever.

retention property

retention: timedelta | None

InklingConfig pydantic-model

Bases: AgentConfig

The in-process pydantic-ai agent, selected by type: inkling: its models, request limit, tool-output handling and permission posture.

Fields:

Source code in octomate/config/agents/inkling.py
class InklingConfig(AgentConfig):
    """The in-process pydantic-ai agent, selected by `type: inkling`: its models,
    request limit, tool-output handling and permission posture."""

    type: Literal["inkling"] = "inkling"

    models: list[ModelConfig] = Field(min_length=1)

    naming_model: ModelConfig | None = Field(
        default=None,
        description="Model used to name sessions alongside each new user turn. "
        "Uses the turn's model when omitted. Resolved through the provider registry "
        "with the same credentials and settings as other models.",
    )

    request_limit: int = Field(
        default=256,
        gt=0,
        description="Maximum model requests in one Inkling run.",
    )

    claims: dict[KnownModelName, Claim] = Field(
        default_factory=dict,
        description="Optional per-model descriptions and supported thinking efforts. "
        "Every configured model is available to bound channels.",
    )
    tool_output: ToolOutputConfig = Field(
        default_factory=ToolOutputConfig,
        description="How oversized tool returns are cut down before they reach — and "
        "stay in — the model's context.",
    )
    permission_mode: InklingPermissionMode = Field(
        default="default",
        description=(
            "Approval posture for an Inkling conversation whose thread is in no "
            "project, or whose project declares none. Claude's scale, narrowed to "
            "what deferred approvals can resolve."
        ),
    )

    @property
    def default_model(self) -> ModelConfig:
        return self.models[0]

type pydantic-field

type: Literal['inkling'] = 'inkling'

models pydantic-field

models: list[ModelConfig]

naming_model pydantic-field

naming_model: ModelConfig | None = None

Model used to name sessions alongside each new user turn. Uses the turn's model when omitted. Resolved through the provider registry with the same credentials and settings as other models.

request_limit pydantic-field

request_limit: int = 256

Maximum model requests in one Inkling run.

claims pydantic-field

claims: dict[KnownModelName, Claim]

Optional per-model descriptions and supported thinking efforts. Every configured model is available to bound channels.

tool_output pydantic-field

tool_output: ToolOutputConfig

How oversized tool returns are cut down before they reach — and stay in — the model's context.

permission_mode pydantic-field

permission_mode: InklingPermissionMode = 'default'

Approval posture for an Inkling conversation whose thread is in no project, or whose project declares none. Claude's scale, narrowed to what deferred approvals can resolve.

default_model property

default_model: ModelConfig