phone-call
Place outbound AI phone calls via Bland AI: book reservations, make appointments, ask businesses questions, then report back the transcript and summary.
git clone --depth 1 https://github.com/Open-Curiosity/gini-agent /tmp/phone-call && cp -r /tmp/phone-call/skills/integrations/phone-call ~/.claude/skills/phone-callSKILL.md
# Phone Call
Place outbound phone calls through Bland AI. An AI voice agent dials the number, follows your task prompt in a live conversation, and the full transcript plus a summary come back when the call ends. Everything runs through `skill_run`:
```
skill_run({ skill: "phone-call", script: "place-call",
args: { phoneNumber: "+15551234567", task: "..." } })
```
The runtime injects `BLAND_API_KEY` into the scripts — you never see or pass the key.
## Workflow
1. **Gather the full task.** Before anything else, collect: who to call (name + number), the goal, hard constraints (dates, times, party size, budget), fallbacks if the first ask isn't available, and the name to give if the callee asks who's calling. A vague task produces a bad call.
2. **Announce, then dial — the approval card is the confirmation.** In one short message, state who you're calling (the exact number) and what the agent will say or ask, then call `place-call` immediately. Do not ask "should I go ahead?" in chat: `place-call` always pauses on an Approve/Deny card, and approving that card is the user's go-ahead. A denied card ends the task; if the user sends a new message with changes, adjust per their feedback and submit a fresh `place-call` — never re-submit the identical call.
3. **Place the call** with `place-call`. It returns `{ ok, callId }`.
4. **Wait for the result** with `check-call`, passing `waitSeconds: 240` — the script polls Bland every 10 seconds internally and returns as soon as the call completes (or when the budget runs out). If the result comes back with `completed: false`, call `check-call` again with the same args and repeat until `completed` is `true`.
5. **Report back** the `summary` and the key points of the `transcript` (quote relevant exchanges, don't dump the whole thing unless asked). Optional: for structured answers about the call (did they confirm? what time?), run `analyze-call`.
If the user wants to abort a call in progress, run `stop-call` with the `callId`.
## Scripts
### place-call
```
skill_run({ skill: "phone-call", script: "place-call", args: {
phoneNumber: "+15551234567", // required, E.164
task: "...", // required, the call prompt
voice: "maya", // optional, Bland voice id/name
firstSentence: "...", // optional, exact opening line
waitForGreeting: true, // default true — wait for the callee to speak first
record: false, // default false — see Rules
maxDurationMinutes: 10, // default 10
language: "en-US" // optional
} })
```
Returns `{ ok, callId }` or `{ ok: false, error }`.
### check-call
```
skill_run({ skill: "phone-call", script: "check-call", args: { callId: "...", waitSeconds: 240 } })
```
Returns `{ ok, callId, status, completed, answeredBy, callLengthMinutes, to, from, transcript, summary, recordingUrl, errorMessage }`. `waitSeconds` (optional, default 0, max 240) makes the script wait for the call to finish, polling Bland every 10 seconds; if the budget runs out first it returns the latest status with `completed: false` — call again with the same args. Fields Bland hasn't populated yet are omitted; `transcript` and `summary` appear only after `completed` is `true`. `callLengthMinutes` is in minutes. `answeredBy` distinguishes `human` from `voicemail`.
### stop-call
```
skill_run({ skill: "phone-call", script: "stop-call", args: { callId: "..." } })
```
Ends an in-progress call. Returns `{ ok, message }`.
### analyze-call
```
skill_run({ skill: "phone-call", script: "analyze-call", args: {
callId: "...", // required
goal: "Book a dinner reservation", // optional context
questions: [ // required, non-empty
["Did they confirm the reservation?", "boolean"],
["What time was booked?", "string"]
]
} })
```
Use after `check-call` reports `completed: true` when you need structured answers instead of reading the transcript — e.g. "Did they confirm the reservation?" → `true`. Each question is a `[question, answerType]` pair (`"string"`, `"boolean"`, `"number"`); a bare string question defaults to `"string"`. Returns `{ ok, answers }` with one answer per question, in order. Each analysis costs Bland credits, so prefer the transcript/summary for simple cases.
## Background watching
The workflow above is synchronous — right for short errand calls where the user is waiting on the result. When the user doesn't want to wait, or the call may run long, hand the waiting to a scheduled job with a `call-watch` pre-run hook: the hook polls Bland with zero model turns while the call is in progress, and wakes the job's turn exactly once, when the call finishes.
1. Place the call with `place-call` as usual and note the `callId`.
2. Create the watcher job:
```
create_job({
name: "call-watch <callId>",
intervalSeconds: 30,
oneShot: false, // must stay false — the silent in-progress ticks would auto-pause a one-shot job before the call finishes
timeoutSeconds: 120,
preRunHook: {
handlerId: "skill-script",
config: { skill: "phone-call", script: "call-watch", callId: "<callId>" }
},
prompt: "A phone call placed in the background has finished. The call result (status, summary, transcript) is in the fenced context items above — report the summary and the key transcript exchanges. Then find this job by name with list_jobs and delete_job it; the call is done and the watcher is no longer needed."
})
```
3. Tell the user the call is underway and the result will arrive in the job's chat thread, then end your turn — do not poll with `check-call`.
Notes:
- The report lands in the job's own chat thread (scheduled jobs deliberately do not post into the main chat).
- `call-watch` is the job's hook script, not for direct `skill_run` use. A failed call (never answered, rejected) also wakes the turn, so the user always hears the outcome.
##How to dogfood and verify a Gini behavior change by driving a real chat turn as a real user would. Use when verifying that the agent reaches for a tool or path on its own — a behavioral steer, a new tool, an INSTRUCTIONS.md change, or a dispatch/provider/memory/skill change — or before claiming a steer "works". Enforces bare, uncoached prompts so the test measures the default, not instruction-following.
Delegate coding work to Claude Code CLI for repository edits, reviews, and multi-turn implementation sessions.
Delegate coding work to the OpenAI Codex CLI for repository changes, reviews, and focused fixes.
Gini's self-knowledge: how Gini configures, extends, and operates on its own state via /api/* and registered tools. Load when the user asks Gini about its own capabilities or asks Gini to modify its own configuration.
Manage Apple Notes via memo CLI: create, search, edit.
Apple Reminders via remindctl: add, list, complete.
Move bytes between Gini upload space, external URLs, and workspace files. Used by every attachment / file-upload / file-download flow regardless of the target system (Linear, GitHub, S3, Notion, etc.).
File a locally-captured, already-redacted Gini crash report as a GitHub issue, with the user's consent. Reads the pending crash queue and delegates the actual filing to the github-issues skill.