# Use AI Integration

> Use when writing or reasoning about code that calls Claude -- an AI-driven app or service, an AI integration, or a skill's scripted model step. Covers the three scenarios (one-shot completion, one-shot agentic task, full agent) and the cost / credentialing model.

- Skill: `imbue-ai/use-ai-integration` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add imbue-ai/use-ai-integration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/imbue-ai/use-ai-integration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: imbue-ai (https://skillmd.com/u/imbue-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/imbue-ai/use-ai-integration

---


# Calling Claude from code

This is the shared reference for the mechanics of calling Claude from code:
which path to use, the call surface, and the cost model. Whatever sent you here
-- building an AI-driven app or service, scripting a skill's `[ai-script]` step, or
adding an AI integration elsewhere -- supplies the framing; this skill is the
how.

Code reaches Claude in one of two ways, depending on whether an
`ANTHROPIC_API_KEY` is configured for the workspace: with a key, call `litellm`
directly; without one, use the `claude -p` helper in `system/scripts/claude_p.py`.

Credentials live in a provider account under `~/.minds/accounts/<id>/`, written
by the provider chooser and pointed at by `CLAUDE_CONFIG_DIR`. NOT in the
process environment -- services inherit a frozen env from supervisord, so an
env-var check goes stale when the user changes auth.

Inside an agent the variable is always set, so a skill script, a worker and the
agent itself all use the account that chat is bound to. Outside one -- a
supervisord service, a cron job -- nothing sets it and `claude_p.py` falls back
to the workspace's most recently used account. Either way, use its resolver
rather than reading a path yourself:

```bash
uv run python -c "from claude_p import read_workspace_ai_credentials; print('keyed' if read_workspace_ai_credentials().api_key else 'keyless')"
```

**Keyed setups snapshot the key at setup time.** When the check says `keyed`,
copy the API key (and the proxy base URL that goes with it) into
`data/.secrets/anthropic.env` as part of setting up the integration, and have
the service load its credentials from there -- `read_workspace_ai_credentials()`
already resolves that file first, so callers using it get this for free. Run
once while setting up:

```bash
uv run python -c "from claude_p import write_anthropic_env_snapshot; print(write_anthropic_env_snapshot())"
```

Only the key + base URL go in the snapshot -- NEVER `CLAUDE_CODE_OAUTH_TOKEN`
(a subscription token cannot authenticate direct API calls, and the writer
refuses it). The snapshot pins the integration: if the user later switches the
workspace's sign-in (e.g. to a subscription), built services keep billing
against the key they were set up with. To re-key or retire an integration,
rewrite or delete `data/.secrets/anthropic.env` when the user asks.

Which path applies rarely changes for a deployment, so **do not branch on it at
call time in simple flows** -- but keyed callers must still resolve the
key/base URL via `read_workspace_ai_credentials()` at each call (not once at
import), so a deliberate re-snapshot takes effect without a service restart.

A caller's model is set **in the code** -- expose it as a top-of-file constant
plus a `--model` override (e.g. `WRITE_MODEL = "claude-haiku-4-5"`), so switching
it is a one-line change. It is independent of the chat's `/model`, which changes
only the conversation.

## Pick the scenario (weakest that does the job)

The call falls into one of three scenarios, by how much agency Claude
needs. Pick the weakest -- it is cheaper, faster, and simpler.

1. **One-shot completion** -- no agency: classify, summarize, extract, rewrite,
   answer-from-context. One prompt, one response, no tools. The common case.
2. **One-shot agentic task** -- a single self-contained job that needs tools or
   file access ("read this file and act", "summarize the diff with the repo
   open"). This is also how you **search the web** -- `claude -p` has a built-in
   `WebSearch` tool.
3. **Full agent** -- a full, possibly long-running agent that runs in its **own
   git worktree** (a `launch-task` worker). Reach for this over scenario 2 when
   Claude edits code that must be tested and validated, or when several agents
   work in the same repo and their changes must not collide. **User- or
   error-triggered only, never an autonomous loop**, with a tightly-scoped task.

## Scenario 1 -- one-shot completion

For a plain completion with **no tools**. If the step needs a tool at all --
web search or otherwise -- reach for an agent (scenario 2), not a server-side
provider tool bolted onto a completion. A server-side tool runs on the provider
that hosts it, welding the step to one vendor, and drags a plain completion onto
a fragile tool code path; an agent's built-in tools have neither problem.

**Keyed (`ANTHROPIC_API_KEY` set): call litellm directly.** It is cheaper than
`claude -p` for non-agentic work, and it gives you structured output, tools,
temperature, etc. with no wrapper of ours in the way. `litellm` is in the root
`pyproject.toml`; read its docs for the call surface. Sketch:

```python
from litellm import completion, completion_cost

from claude_p import read_workspace_ai_credentials  # the file you copied in

# Resolve credentials at call time: the data/.secrets/anthropic.env snapshot
# first (see setup above), then the shared Claude settings, then the process
# env. litellm reads differently-named vars and is picky about a trailing
# slash, so pass both explicitly.
creds = read_workspace_ai_credentials()
api_base = (creds.base_url or "").rstrip("/") or None

resp = completion(
    model="claude-haiku-4-5",
    api_key=creds.api_key,
    api_base=api_base,
    messages=[
        {"role": "system", "content": "You are an email triage classifier."},
        {"role": "user", "content": email_body},
    ],
)
text = resp.choices[0].message.content
cost = completion_cost(completion_response=resp)  # USD for this call
```

**Keyless (no key): copy `system/scripts/claude_p.py` and call `claude_p_completion`.**
It disables tools and runs from an isolated working directory so the repo's
`CLAUDE.md` / `.claude` hooks can't hijack the answer; `system` is required.

```python
from claude_p import claude_p_completion  # the file you copied in

result = claude_p_completion(
    "Classify this email's intent:\n\n" + email_body,
    system="You are an email triage classifier.",   # required
    model="claude-haiku-4-5",
)
print(result.text, result.cost_usd, result.usage)
```

Both `completion` and `claude_p_completion` are synchronous (no asyncio). Once
you have confirmed the prompt + model combination works and produces good
results on a few items, run a batch concurrently with a thread pool
(`concurrent.futures.ThreadPoolExecutor`) rather than one at a time -- the
throughput difference is large. Use enough workers to actually saturate the work
(the calls are I/O-bound, so this can be well into the dozens); back off only if
you hit provider rate limits. When you need structured output, prefer the
provider's own JSON / structured-output mode over parsing free text and retrying
-- it is what keeps the response well-formed.

## Scenario 2 -- one-shot agentic task

Always `claude -p` (it has tools and file access; a plain API call does not), so
this path is the same whether or not a key is set. Copy `system/scripts/claude_p.py` and
call `claude_p_task`: tools stay enabled, it runs in the repo working directory,
and it defaults `permission_mode="bypassPermissions"` (load-bearing -- a headless
run has no human to approve tool use).

```python
from claude_p import claude_p_task

result = claude_p_task(
    "Read data/.apps/email-triage/latest.json and draft a reply using templates/.",
    append_system="Only touch files under data/.apps/email-triage/.",
)
```

`append_system` layers instructions on the default agent; pass `system` to
replace it outright. The default agent prompt is many tokens, but it is useful
instruction for agentic work, so overwrite it only when you have a good reason.
Cost is dominated by per-call overhead, so **batch** items into fewer, larger
calls rather than one call per item.

## Scenario 3 -- full agent

Reach for this over scenario 2 when the work needs its **own git worktree**:
Claude is editing code that has to be tested and validated, or other agents are
working in the same repo and the changes must not collide. A `launch-task` worker
gives the run an isolated branch and worktree; scenario 2 instead runs in the
caller's own working directory.

Launch the worker synchronously and collect its structured result -- do not wrap
it; call the script directly:

```bash
uv run .agents/skills/launch-task/scripts/create_worker.py launch-sync \
  --name email-triage-fix-123 --template worker \
  --runtime-dir data/.apps/email-triage/fix-123 \
  --task-file  data/.apps/email-triage/fix-123/task.md \
  --timeout 30m --result-json data/.apps/email-triage/fix-123/result.json
```

It launches, waits for the worker's finish report in the foreground, writes a JSON
result (`timed_out`, `type`, `name`, `body`, `branch`, `raw_report`) to
`--result-json`, and destroys the worker (the `mngr/<name>` branch survives).
Write the task file first with `finish_report_path` frontmatter
(see the `launch-task` skill). **User- or error-triggered, tightly scoped** -- a
broad unattended launch is how cost and time run away. What to do with the
returned branch (merge, review) is your concern.

## Cost and the keyed onramp

A keyless caller can tell the user what each call costs and what a key would save,
so they can decide when volume justifies setting `ANTHROPIC_API_KEY`:

- `claude_p_completion` / `claude_p_task` return the **actual** `cost_usd` that
  `claude -p` reported, plus the token `usage`.
- Reprice that usage at the keyed model's rate with litellm to estimate the
  savings -- no price table to maintain, litellm carries the prices:

  ```python
  from litellm import cost_per_token

  prompt_cost, completion_cost = cost_per_token(
      model="claude-haiku-4-5",
      prompt_tokens=result.usage.input_tokens,
      completion_tokens=result.usage.output_tokens,
  )
  keyed_estimate = prompt_cost + completion_cost
  savings = result.cost_usd - keyed_estimate   # surface this to suggest a key
  ```

- **Measure before scaling, don't guess.** Run **one real unit** of the work,
  read its **actual** cost and wall-clock off the response (`completion_cost(...)`
  or `result.cost_usd`, not a token estimate), and extrapolate to the full run
  (`N x per-unit`, plus any retries and per-tool/search fees, divided by your pool
  size for wall-clock). Tell the user that projected cost/time before you turn on
  a volume flow.

See [references/billing-and-credentialing.md](references/billing-and-credentialing.md)
for the billing buckets, why `claude -p` costs more than the direct API, the
credentialing model, and the footgun (a stray `ANTHROPIC_API_KEY` switches
`claude -p` to full-API billing).

