Twitter
One account, three lanes: read evidence, plan typed acts, execute an approved
plan.
This is the public branded X (Twitter) catalog skill for the send-as action
family. Its core invariant: the agent may read, audit, draft, and plan freely,
but every act that publishes to a public timeline or mutates the account stops
at a human approval gate, and the sealed receipt proves which acts ran, against
which plan digest, with which provider evidence.
Write for the operator who owns the account, the reviewer who approves a live
act, and downstream skills that consume the resulting packets. Keep each plan
to the smallest evidence-backed act set that satisfies the objective. Existing
post and user ids must come from supplied evidence, never memory or guesswork;
name missing evidence as a blocker.
Composes
data-store#append_event
data-store#read_events
What this skill does
Five runners:
read: collect account evidence from the live API. Queries:
snapshot, posts, mentions, search, following, followers. Emits
twitter.evidence.v1. Read-only; no gate.
read-archive: inspect one contained X archive export (tweets.js,
following.js, or follower.js) through the runtime's digest-bound artifact
page seam. Twitter code receives bounded, record-complete pages and never a
path or whole-file escape hatch. The runtime snapshots up to 512 MiB and this
skill requests 512 KiB pages; the page size is not a total archive limit.
Emits the same twitter.evidence.v1 packet; no gate.
plan: turn one bounded objective plus evidence into twitter.plan.v1, an
explicit list of typed acts with rationale, using agent judgment, then bind
the exact plan through native data.digest. This is the curated lane, for
dozens of acts that each deserve a reason. A plan is a draft; it delivers
nothing. The result exposes twitter_plan.data and the matching
digest_result.data.digest required by execute; callers never calculate
the digest themselves.
select: the bulk lane. Apply a deterministic predicate to an archive export
and emit a compact plan, no agent judgment. Use it when the criterion is
mechanical and the match set runs to thousands, where a per-item rationale
would be wrong and would exceed the runtime output limit. Two targets:
posts pages tweets.js and emits delete_post acts (predicate: author,
date range, engagement threshold), with identical results across page sizes;
users reads a bounded following.js bundle and emits
unfollow acts (predicate: non_mutual: true for accounts you follow that
do not follow back, needing follower.js too, or an explicit user_ids
list). Emits twitter.selection.v1 carrying a digest-bound twitter_plan.
execute: run an approved plan through the X API behind an approval gate,
act by act, sealing per-act provider evidence into twitter.execution.v1 and
appending one compact progress fact to a durable execution ledger. A batch
is always a contiguous plan prefix and native HTTP stops at the first failed
or rate-limited request, so a monotonic cursor—not an accumulated id list—is
enough to resume safely.
Resume and the execution ledger
execute is stateless per turn; the state lives in a data-store stream keyed
by the immutable plan digest. The latest twitter.execution.progress.v1 event
contains only next_act_index, total_act_count, an optional in-progress
thread segment cursor, and a bounded summary of the last batch. Its size is
constant whether the plan has completed two acts or two thousand.
The driver reads only the latest ledger event (read_events, limit: 1) and
its stream version. It calls execute with that expected_version and the
canonical key twitter:<plan_digest>:v<expected_version+1>. The runner performs
the same tail read itself and refuses stale or misbound inputs before approval
or provider work. After a committed batch, the driver repeats with the new
version until next_act_index == total_act_count. It never scans history or
folds completed ids. An invalid plan, digest mismatch, stale cursor, or already
complete plan produces a sealed refusal/no-op receipt but does not append a
fake state transition.
Threads use one extra bounded cursor: if segment 1 succeeds and segment 2
fails, progress records the next segment plus the confirmed reply id. The next
batch starts at segment 2 rather than reposting segment 1. The operator binds
data_source_ref to durable local SQLite or another declared data-store
adapter; the skill never opens the database directly.
Each runner emits exactly one packet for its lane: typed evidence with
provenance and a content digest, a curated or bulk act plan bound by digest, or
provider outcomes bound to the executed plan digest and appended to the ledger.
The act vocabulary, with its consequence class:
| kind |
consequence |
params |
post |
public_send |
text |
reply |
public_send |
text, in_reply_to |
quote |
public_send |
text, quote_of |
thread |
public_send |
texts (ordered, max 25) |
repost |
public_send |
post_id |
delete_post |
live_mutation |
post_id |
unfollow |
live_mutation |
target_user_id |
mute |
live_mutation |
target_user_id |
block |
live_mutation |
target_user_id |
follow |
live_mutation |
target_user_id |
like |
live_mutation |
post_id |
Every kind is consequential, so every plan sets
gates.human_approval_required: true. follow, like, and repost are
engagement acts and share a hard cap of 10 per execution. Direct messages are
outside this skill.
When to use this skill
- Promote a release, publish a thread, or reply to a mention on behalf of a
named principal, with content bound verbatim in the plan.
- Audit an account's own post history or following list and prune it: bulk
delete old posts, unfollow low-value accounts, against operator-stated
criteria.
- Gather timeline, mention, or search evidence for downstream research,
triage, or lead skills that consume
twitter.evidence.v1.
- Route a
send-as plan onto X as its provider delivery lane.
When not to use this skill
- To send direct messages or manage DM conversations.
- To farm engagement: follow-churn, mass liking, coordinated reposting, or any
volume pattern designed to game visibility. The engagement cap is not a
budget to fill.
- To mention, reply to, or dogpile users who have not engaged with the
principal, at volume.
- To operate an account the operator does not control, or to automate consumer
account creation.
- To bypass the approval gate, the act caps, or the operator's spending limit
on the pay-per-use API.
Procedure
For the plan runner, build twitter_plan this way:
- Hold one bounded objective. If the ask bundles unrelated jobs (a promo
thread plus a follow purge), return
needs_input with the split.
- Ground every reference. Ids for
delete_post, unfollow, reply,
quote, like, mute, and block must come from evidence_json or an
explicit operator-supplied id. If the evidence is missing or stale, name it
as a blocker; never invent or approximate an id.
- Write public content into the act params verbatim: the exact
text or
texts to publish, shaped by brand_context when supplied. Do not plan a
summary of what will be written; approval binds the exact words.
- Give every act a stable
act_id (act-001 onward), its kind, params,
its consequence from the table, and a one-line rationale tied to the
objective or the operator's stated criteria.
- Apply
operator_policy narrowly. A prune criterion like "no engagement and
older than 2023" selects only posts the evidence shows meet it; borderline
items go to open_questions, not into the act list.
- Keep the plan small: at most 50 acts, at most 10 engagement acts, threads
at most 25 segments. A larger job becomes staged plans.
- Set
decision: ready when acts are grounded and complete; needs_input
for missing objective, principal, evidence, or content; reject for asks
outside the vocabulary or inside the abuse boundaries.
- Never place credentials, bearer tokens, raw API dumps, or third-party
personal data beyond public ids and handles in the plan.
Edge cases and stop conditions
- Mixed objective: return
needs_input with the runner or plan split.
- Id not in evidence: blocker; the plan stays
needs_input.
- Prune criteria ambiguous: select the unambiguous items, list the rest in
open_questions.
- Rate limit during read: the evidence packet returns what it collected
with
stop_conditions: ["rate_limited"] and the reset time.
- Oversized user-selection archive: the current two-file
users selector
refuses when either bounded text file exceeds the native read limit; it never
treats truncation as an empty following/follower set. read-archive itself
remains paged. Do not claim an oversized users bulk plan was evaluated.
- Rate limit during execute: the packet reports
next_act_index,
remaining_count, and the reset time. After the reset, read the latest
ledger version and invoke the next canonical batch; do not reconstruct ids.
- Plan digest mismatch on execute: the apply step refuses and executes
nothing. A refusal is receipt evidence, not a fake ledger transition.
- Stale stream version or batch key: refusal before the approval/provider
boundary. Re-read the one-event ledger tail and derive the canonical key.
- Partially posted thread: resume from the ledger's
active_thread cursor;
never restart the thread from its first confirmed segment.
- Approval missing or denied: the execute graph stops at the gate.
- Credentials missing: clean
needs_input stop naming the environment
variables; never a half-configured call.
- Fully autonomous live posting requested:
reject; the gate is the
contract.
Output schema
twitter.evidence.v1: decision, source (live or archive), query,
account, items[] (typed post or user records with metrics), item_count,
truncated, provenance (retrieved_via, request_count,
content_digest), rate, blockers[], stop_conditions[].
twitter.plan.v1 (returned as twitter_plan.data together with
digest_result.data.digest): decision (ready, needs_input, reject),
objective, principal, acts[] (act_id, kind, params,
consequence, rationale), gates
(human_approval_required, approval_ref), evidence_refs[],
optional context_bindings[] (packet_digest, applied_rules[]) preserving
the exact brand or taste context applied by the planner, open_questions[],
blockers[], success_checkpoint.
twitter.selection.v1: decision, objective, principal, predicate,
matched, scanned, truncated, twitter_plan (a twitter.plan.v1),
plan_digest, blockers[]. The twitter_plan is what a driver hands to
execute, and plan_digest is bound so the approved and executed bytes are
provably identical.
twitter.execution.v1: decision (executed, partial, stopped,
refused), plan_digest, principal, results[] (act_id, kind,
consequence, status, provider_ref, detail) for the current bounded
batch, next_act_index, total_act_count, remaining_count, optional
active_thread, rate, blockers[], and success_checkpoint. A ready batch
also carries the compact twitter.execution.progress.v1 ledger delta.
Worked example
Input: objective "delete my zero-engagement posts from before 2024",
principal account:@example, evidence from
read-archive(query: posts, archive_file: data/tweets.js) showing three matching
posts, operator_policy "keep anything with replies".
Output: decision: ready; three delete_post acts, each carrying the post id
from the evidence and a rationale quoting its age and zero metrics;
gates.human_approval_required: true; one borderline post with two replies
listed in open_questions. Execution then stops at the approval gate, and the
sealed receipt binds the approved digest to the three deletions the provider
confirmed.
Inputs
read: query (required), params, max_items, auth.
read-archive: query and relative archive_file (required),
archive_base (workspace default, or skill for packaged fixtures), and
max_items.
plan: objective (required), principal (required), evidence_json,
operator_policy, brand_context, operator_context.
select: objective (required), principal (required), target (posts
default, or users), archive_file (posts), following_file and
followers_file (users), predicate (required: posts take rt_of,
is_retweet, text_prefix, text_contains, max_likes, max_reposts,
before_year, after_year; users take non_mutual or user_ids),
archive_base, max_acts.
execute: plan_json and plan_digest (required), data_source_ref
(required), expected_version (required), idempotency_key (required),
max_acts (hard-capped at 50). idempotency_key must be exactly
twitter:<plan_digest>:v<expected_version+1>; the runner independently
verifies both values against the durable one-event tail before execution.
Credentials and cost
Credentials are delivered per run through the runx credential envelope, never
as inputs and never as receipt material. Two materials exist:
TWITTER_USER_AUTH: one JSON object holding consumer_key,
consumer_secret, access_token, access_secret (OAuth 1.0a user
context). Required for every mutation and for own-account reads. Store it
with runx credential set twitter --profile twitter-user --auth-mode oauth1_user --from-stdin.
TWITTER_BEARER_TOKEN: the app-context bearer token, enough for search
and public reads. Store it with runx credential set twitter --profile twitter-app --auth-mode bearer --from-stdin. Read-only app runs should use
only this profile.
Use --profile twitter-user with read --auth user and every execute run;
use --profile twitter-app with read --auth app. The runner contract maps the
selected profile's auth mode to exactly one delivery variable. Tool permission
allowlists do not carry credentials. For local development, the same declared
variable can come from the process or workspace .env; if both Twitter
variables are set, Runx refuses the ambiguous selection.
The X API bills per request on current plans and a post containing a link
costs a large multiple of a plain one, so prefer archive exports for bulk
history, set a spending cap in the developer portal, and let max_items and
the act caps bound each run.
Agent task contract
twitter-plan
Follow the plan procedure and act vocabulary above. Return one
twitter_plan containing decision, objective, principal, typed acts,
approval gates, evidence refs, open questions, blockers, and a truthful success
checkpoint. Every referenced post or user id must be grounded in supplied
evidence, and every public word must appear verbatim in the act params. A plan
never executes, publishes, deletes, follows, or otherwise mutates the account.
1---2name: twitter3description: Govern X (Twitter) account work through three lanes: evidence reads, typed act plans for posting and account hygiene, and gated execution with per-act provider evidence. Nothing reaches a live timeline or mutates an account without an explicit approval recorded in the receipt.4---56# Twitter78One account, three lanes: read evidence, plan typed acts, execute an approved9plan.1011This is the public branded X (Twitter) catalog skill for the `send-as` action12family. Its core invariant: the agent may read, audit, draft, and plan freely,13but every act that publishes to a public timeline or mutates the account stops14at a human approval gate, and the sealed receipt proves which acts ran, against15which plan digest, with which provider evidence.1617Write for the operator who owns the account, the reviewer who approves a live18act, and downstream skills that consume the resulting packets. Keep each plan19to the smallest evidence-backed act set that satisfies the objective. Existing20post and user ids must come from supplied evidence, never memory or guesswork;21name missing evidence as a blocker.2223## Composes2425<!-- Generated from the native execution closure; run pnpm core-skills:composes:generate. -->2627- `data-store#append_event`28- `data-store#read_events`2930## What this skill does3132Five runners:3334- `read`: collect account evidence from the live API. Queries:35 `snapshot`, `posts`, `mentions`, `search`, `following`, `followers`. Emits36 `twitter.evidence.v1`. Read-only; no gate.37- `read-archive`: inspect one contained X archive export (`tweets.js`,38 `following.js`, or `follower.js`) through the runtime's digest-bound artifact39 page seam. Twitter code receives bounded, record-complete pages and never a40 path or whole-file escape hatch. The runtime snapshots up to 512 MiB and this41 skill requests 512 KiB pages; the page size is not a total archive limit.42 Emits the same `twitter.evidence.v1` packet; no gate.43- `plan`: turn one bounded objective plus evidence into `twitter.plan.v1`, an44 explicit list of typed acts with rationale, using agent judgment, then bind45 the exact plan through native `data.digest`. This is the curated lane, for46 dozens of acts that each deserve a reason. A plan is a draft; it delivers47 nothing. The result exposes `twitter_plan.data` and the matching48 `digest_result.data.digest` required by `execute`; callers never calculate49 the digest themselves.50- `select`: the bulk lane. Apply a deterministic predicate to an archive export51 and emit a compact plan, no agent judgment. Use it when the criterion is52 mechanical and the match set runs to thousands, where a per-item rationale53 would be wrong and would exceed the runtime output limit. Two targets:54 `posts` pages `tweets.js` and emits `delete_post` acts (predicate: author,55 date range, engagement threshold), with identical results across page sizes;56 `users` reads a bounded `following.js` bundle and emits57 `unfollow` acts (predicate: `non_mutual: true` for accounts you follow that58 do not follow back, needing `follower.js` too, or an explicit `user_ids`59 list). Emits `twitter.selection.v1` carrying a digest-bound `twitter_plan`.60- `execute`: run an approved plan through the X API behind an approval gate,61 act by act, sealing per-act provider evidence into `twitter.execution.v1` and62 appending one compact progress fact to a durable execution ledger. A batch63 is always a contiguous plan prefix and native HTTP stops at the first failed64 or rate-limited request, so a monotonic cursor—not an accumulated id list—is65 enough to resume safely.6667### Resume and the execution ledger6869`execute` is stateless per turn; the state lives in a `data-store` stream keyed70by the immutable plan digest. The latest `twitter.execution.progress.v1` event71contains only `next_act_index`, `total_act_count`, an optional in-progress72thread segment cursor, and a bounded summary of the last batch. Its size is73constant whether the plan has completed two acts or two thousand.7475The driver reads only the latest ledger event (`read_events`, `limit: 1`) and76its stream version. It calls `execute` with that `expected_version` and the77canonical key `twitter:<plan_digest>:v<expected_version+1>`. The runner performs78the same tail read itself and refuses stale or misbound inputs before approval79or provider work. After a committed batch, the driver repeats with the new80version until `next_act_index == total_act_count`. It never scans history or81folds completed ids. An invalid plan, digest mismatch, stale cursor, or already82complete plan produces a sealed refusal/no-op receipt but does not append a83fake state transition.8485Threads use one extra bounded cursor: if segment 1 succeeds and segment 286fails, progress records the next segment plus the confirmed reply id. The next87batch starts at segment 2 rather than reposting segment 1. The operator binds88`data_source_ref` to durable local SQLite or another declared `data-store`89adapter; the skill never opens the database directly.9091Each runner emits exactly one packet for its lane: typed evidence with92provenance and a content digest, a curated or bulk act plan bound by digest, or93provider outcomes bound to the executed plan digest and appended to the ledger.9495The act vocabulary, with its consequence class:9697| kind | consequence | params |98| --- | --- | --- |99| `post` | public_send | `text` |100| `reply` | public_send | `text`, `in_reply_to` |101| `quote` | public_send | `text`, `quote_of` |102| `thread` | public_send | `texts` (ordered, max 25) |103| `repost` | public_send | `post_id` |104| `delete_post` | live_mutation | `post_id` |105| `unfollow` | live_mutation | `target_user_id` |106| `mute` | live_mutation | `target_user_id` |107| `block` | live_mutation | `target_user_id` |108| `follow` | live_mutation | `target_user_id` |109| `like` | live_mutation | `post_id` |110111Every kind is consequential, so every plan sets112`gates.human_approval_required: true`. `follow`, `like`, and `repost` are113engagement acts and share a hard cap of 10 per execution. Direct messages are114outside this skill.115116## When to use this skill117118- Promote a release, publish a thread, or reply to a mention on behalf of a119 named principal, with content bound verbatim in the plan.120- Audit an account's own post history or following list and prune it: bulk121 delete old posts, unfollow low-value accounts, against operator-stated122 criteria.123- Gather timeline, mention, or search evidence for downstream research,124 triage, or lead skills that consume `twitter.evidence.v1`.125- Route a `send-as` plan onto X as its provider delivery lane.126127## When not to use this skill128129- To send direct messages or manage DM conversations.130- To farm engagement: follow-churn, mass liking, coordinated reposting, or any131 volume pattern designed to game visibility. The engagement cap is not a132 budget to fill.133- To mention, reply to, or dogpile users who have not engaged with the134 principal, at volume.135- To operate an account the operator does not control, or to automate consumer136 account creation.137- To bypass the approval gate, the act caps, or the operator's spending limit138 on the pay-per-use API.139140## Procedure141142For the `plan` runner, build `twitter_plan` this way:1431441. Hold one bounded objective. If the ask bundles unrelated jobs (a promo145 thread plus a follow purge), return `needs_input` with the split.1462. Ground every reference. Ids for `delete_post`, `unfollow`, `reply`,147 `quote`, `like`, `mute`, and `block` must come from `evidence_json` or an148 explicit operator-supplied id. If the evidence is missing or stale, name it149 as a blocker; never invent or approximate an id.1503. Write public content into the act params verbatim: the exact `text` or151 `texts` to publish, shaped by `brand_context` when supplied. Do not plan a152 summary of what will be written; approval binds the exact words.1534. Give every act a stable `act_id` (`act-001` onward), its `kind`, `params`,154 its `consequence` from the table, and a one-line `rationale` tied to the155 objective or the operator's stated criteria.1565. Apply `operator_policy` narrowly. A prune criterion like "no engagement and157 older than 2023" selects only posts the evidence shows meet it; borderline158 items go to `open_questions`, not into the act list.1596. Keep the plan small: at most 50 acts, at most 10 engagement acts, threads160 at most 25 segments. A larger job becomes staged plans.1617. Set `decision`: `ready` when acts are grounded and complete; `needs_input`162 for missing objective, principal, evidence, or content; `reject` for asks163 outside the vocabulary or inside the abuse boundaries.1648. Never place credentials, bearer tokens, raw API dumps, or third-party165 personal data beyond public ids and handles in the plan.166167## Edge cases and stop conditions168169- **Mixed objective:** return `needs_input` with the runner or plan split.170- **Id not in evidence:** blocker; the plan stays `needs_input`.171- **Prune criteria ambiguous:** select the unambiguous items, list the rest in172 `open_questions`.173- **Rate limit during read:** the evidence packet returns what it collected174 with `stop_conditions: ["rate_limited"]` and the reset time.175- **Oversized user-selection archive:** the current two-file `users` selector176 refuses when either bounded text file exceeds the native read limit; it never177 treats truncation as an empty following/follower set. `read-archive` itself178 remains paged. Do not claim an oversized users bulk plan was evaluated.179- **Rate limit during execute:** the packet reports `next_act_index`,180 `remaining_count`, and the reset time. After the reset, read the latest181 ledger version and invoke the next canonical batch; do not reconstruct ids.182- **Plan digest mismatch on execute:** the apply step refuses and executes183 nothing. A refusal is receipt evidence, not a fake ledger transition.184- **Stale stream version or batch key:** refusal before the approval/provider185 boundary. Re-read the one-event ledger tail and derive the canonical key.186- **Partially posted thread:** resume from the ledger's `active_thread` cursor;187 never restart the thread from its first confirmed segment.188- **Approval missing or denied:** the execute graph stops at the gate.189- **Credentials missing:** clean `needs_input` stop naming the environment190 variables; never a half-configured call.191- **Fully autonomous live posting requested:** `reject`; the gate is the192 contract.193194## Output schema195196- `twitter.evidence.v1`: `decision`, `source` (`live` or `archive`), `query`,197 `account`, `items[]` (typed post or user records with metrics), `item_count`,198 `truncated`, `provenance` (`retrieved_via`, `request_count`,199 `content_digest`), `rate`, `blockers[]`, `stop_conditions[]`.200- `twitter.plan.v1` (returned as `twitter_plan.data` together with201 `digest_result.data.digest`): `decision` (`ready`, `needs_input`, `reject`),202 `objective`, `principal`, `acts[]` (`act_id`, `kind`, `params`,203 `consequence`, `rationale`), `gates`204 (`human_approval_required`, `approval_ref`), `evidence_refs[]`,205 optional `context_bindings[]` (`packet_digest`, `applied_rules[]`) preserving206 the exact brand or taste context applied by the planner, `open_questions[]`,207 `blockers[]`, `success_checkpoint`.208- `twitter.selection.v1`: `decision`, `objective`, `principal`, `predicate`,209 `matched`, `scanned`, `truncated`, `twitter_plan` (a `twitter.plan.v1`),210 `plan_digest`, `blockers[]`. The `twitter_plan` is what a driver hands to211 `execute`, and `plan_digest` is bound so the approved and executed bytes are212 provably identical.213- `twitter.execution.v1`: `decision` (`executed`, `partial`, `stopped`,214 `refused`), `plan_digest`, `principal`, `results[]` (`act_id`, `kind`,215 `consequence`, `status`, `provider_ref`, `detail`) for the current bounded216 batch, `next_act_index`, `total_act_count`, `remaining_count`, optional217 `active_thread`, `rate`, `blockers[]`, and `success_checkpoint`. A ready batch218 also carries the compact `twitter.execution.progress.v1` ledger delta.219220## Worked example221222Input: objective "delete my zero-engagement posts from before 2024",223principal `account:@example`, evidence from224`read-archive(query: posts, archive_file: data/tweets.js)` showing three matching225posts, operator_policy "keep anything with replies".226227Output: `decision: ready`; three `delete_post` acts, each carrying the post id228from the evidence and a rationale quoting its age and zero metrics;229`gates.human_approval_required: true`; one borderline post with two replies230listed in `open_questions`. Execution then stops at the approval gate, and the231sealed receipt binds the approved digest to the three deletions the provider232confirmed.233234## Inputs235236- `read`: `query` (required), `params`, `max_items`, `auth`.237- `read-archive`: `query` and relative `archive_file` (required),238 `archive_base` (`workspace` default, or `skill` for packaged fixtures), and239 `max_items`.240- `plan`: `objective` (required), `principal` (required), `evidence_json`,241 `operator_policy`, `brand_context`, `operator_context`.242- `select`: `objective` (required), `principal` (required), `target` (`posts`243 default, or `users`), `archive_file` (posts), `following_file` and244 `followers_file` (users), `predicate` (required: posts take `rt_of`,245 `is_retweet`, `text_prefix`, `text_contains`, `max_likes`, `max_reposts`,246 `before_year`, `after_year`; users take `non_mutual` or `user_ids`),247 `archive_base`, `max_acts`.248- `execute`: `plan_json` and `plan_digest` (required), `data_source_ref`249 (required), `expected_version` (required), `idempotency_key` (required),250 `max_acts` (hard-capped at 50). `idempotency_key` must be exactly251 `twitter:<plan_digest>:v<expected_version+1>`; the runner independently252 verifies both values against the durable one-event tail before execution.253254## Credentials and cost255256Credentials are delivered per run through the runx credential envelope, never257as inputs and never as receipt material. Two materials exist:258259- `TWITTER_USER_AUTH`: one JSON object holding `consumer_key`,260 `consumer_secret`, `access_token`, `access_secret` (OAuth 1.0a user261 context). Required for every mutation and for own-account reads. Store it262 with `runx credential set twitter --profile twitter-user --auth-mode263 oauth1_user --from-stdin`.264- `TWITTER_BEARER_TOKEN`: the app-context bearer token, enough for `search`265 and public reads. Store it with `runx credential set twitter --profile266 twitter-app --auth-mode bearer --from-stdin`. Read-only app runs should use267 only this profile.268269Use `--profile twitter-user` with `read --auth user` and every `execute` run;270use `--profile twitter-app` with `read --auth app`. The runner contract maps the271selected profile's auth mode to exactly one delivery variable. Tool permission272allowlists do not carry credentials. For local development, the same declared273variable can come from the process or workspace `.env`; if both Twitter274variables are set, Runx refuses the ambiguous selection.275276The X API bills per request on current plans and a post containing a link277costs a large multiple of a plain one, so prefer archive exports for bulk278history, set a spending cap in the developer portal, and let `max_items` and279the act caps bound each run.280281## Agent task contract282283### `twitter-plan`284285Follow the `plan` procedure and act vocabulary above. Return one286`twitter_plan` containing `decision`, `objective`, `principal`, typed `acts`,287approval gates, evidence refs, open questions, blockers, and a truthful success288checkpoint. Every referenced post or user id must be grounded in supplied289evidence, and every public word must appear verbatim in the act params. A plan290never executes, publishes, deletes, follows, or otherwise mutates the account.