Pixir.Tool behaviour (pixir v0.1.0)

Copy Markdown View Source

The Tool behaviour (CONTEXT.md; the Kimojo-shaped contract minus risk_level).

A Tool declares itself via __tool__/0 and runs via execute/2. Per ADR 0005 every Tool is also dry-runnable: use Pixir.Tool injects a default dry_run/2 that reports the planned action without side effects; side-effecting Tools override it with a richer (still effect-free) plan.

__tool__/0

%{
  name: "read",
  description: "Read a file from the workspace",
  parameters: %{                       # JSON Schema (string keys)
    "type" => "object",
    "properties" => %{"path" => %{"type" => "string"}},
    "required" => ["path"]
  }
}

execute/2 and dry_run/2

Receive args (a string-keyed map, validated by the Executor) and a context %{session_id, workspace, call_id, dry_run}. They return:

  • {:ok, result}result is a string-keyed map; "output" (a string) is the model-facing payload (token-bounded via truncate/2, ADR 0005).
  • {:error, structured} — the standard %{ok: false, error: %{kind, message, details}} envelope (use error/3).

Summary

Types

The curated, stable error-kind vocabulary (ADR 0005, rule 3). Callers and the model branch on these, so the set is documented here as the single source of truth — adding a kind is a deliberate change, not an ad-hoc string. Grouped by origin

Functions

Build the standard structured error envelope (ADR 0005). kind should be a member of the documented kind/0 vocabulary so callers can branch on it.

Token-bound a model-facing string (ADR 0005). Truncates to max bytes with an explicit marker so the model knows output was cut.

Types

context()

@type context() :: %{
  :session_id => String.t(),
  :workspace => String.t(),
  :call_id => String.t(),
  optional(:dry_run) => boolean()
}

kind()

@type kind() ::
  :invalid_args
  | :unknown_tool
  | :outside_workspace
  | :not_found
  | :resource_missing
  | :no_match
  | :not_unique
  | :read_failed
  | :write_failed
  | :command_failed
  | :timeout
  | :permission_denied
  | :detached
  | :iteration_cap
  | :tool_result_record_failed
  | :corrupt_log_line
  | :ephemeral_not_loggable
  | :log_encode_failed
  | :log_read_failed
  | :log_write_failed
  | :provider_http_error
  | :model_not_supported
  | :usage_limit_reached
  | :rate_limited
  | :network
  | :not_authenticated
  | :token_request_failed
  | :no_account_id
  | :invalid_response
  | :corrupt_auth
  | :auth_read_failed
  | :auth_write_failed
  | :device_auth_failed
  | :device_code_unsupported
  | :session_start_failed
  | :no_prompt
  | :stdin_error

The curated, stable error-kind vocabulary (ADR 0005, rule 3). Callers and the model branch on these, so the set is documented here as the single source of truth — adding a kind is a deliberate change, not an ad-hoc string. Grouped by origin:

  • tools / executor:invalid_args, :unknown_tool, :outside_workspace, :not_found, :resource_missing, :no_match, :not_unique, :read_failed, :write_failed, :command_failed, :timeout, :permission_denied, :detached
  • turn loop:iteration_cap, :tool_result_record_failed
  • log:corrupt_log_line, :ephemeral_not_loggable, :log_encode_failed, :log_read_failed, :log_write_failed
  • provider:provider_http_error, :model_not_supported, :usage_limit_reached, :rate_limited, :network
  • auth:not_authenticated, :token_request_failed, :no_account_id, :invalid_response, :corrupt_auth, :auth_read_failed, :auth_write_failed, :device_auth_failed, :device_code_unsupported, :session_start_failed
  • cli / stdin:no_prompt, :stdin_error

Note: a bash command that runs but exits nonzero is not an error — it returns a successful result %{"output", "exit_code", "ok" => false} so the model can read the output and decide (a no-match grep exiting 1 is normal). See ADR 0005.

result()

@type result() :: {:ok, map()} | {:error, map()}

spec()

@type spec() :: %{name: String.t(), description: String.t(), parameters: map()}

Callbacks

dry_run(args, context)

@callback dry_run(args :: map(), context()) :: result()

execute(args, context)

@callback execute(args :: map(), context()) :: result()

Functions

error(kind, message, details \\ %{})

@spec error(kind() | atom(), String.t(), map()) :: map()

Build the standard structured error envelope (ADR 0005). kind should be a member of the documented kind/0 vocabulary so callers can branch on it.

truncate(text, max \\ 16000)

@spec truncate(binary(), pos_integer()) :: binary()

Token-bound a model-facing string (ADR 0005). Truncates to max bytes with an explicit marker so the model knows output was cut.