본문으로 건너뛰기
S

dsh-ig-life

sakura4u/dsh-ig-life

把 DeepSeek 養進 Instagram — a DSH plugin that gives an agent an IG life

설치

dsh plugin --profile web add github:sakura4u/dsh-ig-life

README

English | 中文

dsh-ig-life

Raise a DeepSeek agent on Instagram
An IG body · a memory that fades · a life that keeps running when nobody is talking

CI License Release Node Dependencies DSH


One-minute demo: no Instagram account, no API key — this only shows how it talks. Real DMs need one of two bodies: the official one (an Instagram professional account, a Meta app long-lived token and a public HTTPS tunnel — docs/IG-SETUP.md), or the unofficial one (a plain personal account through a local Python sidecar — docs/UNOFFICIAL.md; it violates Instagram's terms and can get the account banned). The plugin itself never receives your password.

git clone https://github.com/sAKURA4u/dsh-ig-life && cd dsh-ig-life
node scripts/self-check.mjs

Real output below, verbatim — the bundled persona speaks Cantonese. The English is my annotation, not the program's.

── 1. 收到私訊 ──  (an inbound DM)
  · [dryrun] dm → dafish_owner:我啱啱喺窗邊睇雨。你有冇帶傘   ← "I was watching the rain. Did you bring an umbrella"
  同一對話即時再問:handled=0(被冷卻擋住)                    ← same thread again: thread cooldown
  重複投遞:ingested=0(被擋 duplicate)                        ← replayed webhook: deduped
  陌生人:handled=0(allowlist 外預設唔回)                     ← a stranger: ignored by default

── 2. 生命時鐘(主動搵你)──  (the life clock: it reaches out first)
  主動訊息:「喂,你今日有冇食飯」                              ← "hey, have you eaten today"
  狀態:出世第 1 日|心情:有啲坐唔定|體力:69/100|親近感:32/100

── 3. 可塑性記憶 ──  (plastic memory)
  總數 9 條|錨點 8、重要 0、眼前 1、模糊 0                     ← 9 total · anchor 8 · important 0 · recent 1 · fading 0

This is not a support bot. It reaches out first, leaves you on read, refuses you, forgets what doesn't matter — and three days after you went quiet it asks whether you're still there.


✨ What it does

💬 Instagram DMsa webhook arrives → becomes a real DSH session → the model decides whether and how to answer
🧠 Plastic memoryremembers the beginning, the recent, the genuinely important; the rest fades, with a nightly "sleep compression"
🌙 A daily rhythmenergy, mood, sleep. While awake it may reach out first or publish a diary post
🎙 Voicetext → TTS → public mp3 → IG audio attachment. [laugh] [whisper] map to speech rate and are never spoken aloud
🖼 Stickerscollect images people send, tag them, send the fitting one — numbers are never reused
🎬 Reelsread metadata + cover through a vision endpoint; sharing just sends the link and Instagram renders the card
📚 Slangsubmit → the owner approves with /slang ok <id> → only then injected
👤 Per-person memoryimpressions, open topics and details per handle; cross-thread leakage is blocked in code
🎞 Storiesig_story: a photo story, caption optional
🪞 Its own profileig_profile: display name, bio, avatar
⏳ A human reply pacea random 2–12 s pause before it starts thinking, never instant

🧠 Memory: forgetting is what makes it human

It remembers the beginning, the recent, and the genuinely important — the rest fades day by day.

TierWhat lands hereForgotten?
anchorpinned, identity, the first 8never
importantscore ≥ 0.75, or recalled ≥ 3 timesnever — and it gets stronger with use
recentthe newest 12only after leaving the window
fadingeverything elseexp(-age/21d), pruned per day each night

No vector database, no embedding API, no tokenizer — CJK character bigrams plus IDF. How it works →

⚡ Install

dsh plugin --profile web add /path/to/dsh-ig-life

Restart DSH and you should see:

[ig-life] ready · transport=dryrun · engine=agent · tools=21 · webhook=/ig-life/webhook · poll=off · life=off · memory=7

Dry-run by default: nothing leaves the machine, everything lands in ~/.dsh/ig-life/outbox.jsonl. Read it for a week. Only then switch to a real body.

Three bodies, one character

transportAccountNeedsThe catch
dryrunnonenothingsends nothing; a transcript only
graphCreator/BusinessMeta app, long-lived token, public HTTPSApp Review for other people, API rate limits
unofficialany personal accountPython 3.9+, instagrapi, a local sidecarviolates Instagram's terms; the account can be banned

unofficial is lib/transport/unofficial.js talking to src/ig_bridge.py over 127.0.0.1. The password and the session cookie never enter the Node process, its config, its logs or a prompt — and the sidecar is the thing that decides how fast Instagram is touched. The full warning and setup →

⚙️ Settings, in the GUI

The plugin contributes its own page to DSH → Settings → Instagram 人生:

  • Account — the character's IG username and password, plus Save and log in. The sidecar owns the file (ig-secrets.json, 0600); the password passes through the plugin process on its way there but is never logged, stored or echoed, and the page's field is cleared after saving. Once stored, a restart reuses the session automatically.
  • Behaviour — transport, owner handle, allowlist, the daily rhythm, the caps and the sidecar address. Saving writes <dataDir>/settings.json (only those fields — never a token) and the page says plainly that a restart applies it.
  • Every field carries a sentence explaining what it does and what it costs.

📦 Handing it to someone else

  1. They install the plugin: dsh plugin --profile web add <repo> (or the desktop app's plugin manager).
  2. They only need Python (3.9+). The settings page's Install dependencies button runs python -m pip install -r src/requirements.txt for them (or they can run it in the repo).
  3. DSH → Settings → Instagram 人生: type the account, Save and log in, press switch to unofficial, restart.

The plugin starts and adopts the sidecar itself (unofficial.autoStart), so nobody has to keep a terminal open; the page shows the Python/dependency status and offers start/restart. The declared compatibility range covers DSH 0.1.x and 0.2.x, so a fresh install no longer needs a hand-written compatibility.json.

The same data is editable from /ig-life/console if the page is not mounted (a bundle that fails to register leaves the rest of Settings alone: the page is wrapped in its own error boundary).

📝 Configuration (click)

Override the row in ~/.dsh/profiles/web/cordis.patch.yml:

- id: ig-life
  config:
    transport: dryrun          # dryrun | graph | unofficial
    ownerHandle: your_main_account
    allowlist: [your_main_account]
    graph:
      accessToken: ''          # or the IG_LIFE_GRAPH_TOKEN env var
      igUserId: ''
      appSecret: ''
      verifyToken: ''
      publicBaseUrl: ''        # required for images and voice
    unofficial:                # only used by transport: unofficial
      baseUrl: http://127.0.0.1:8737
      pollSeconds: 20          # floor 10 — a faster poll is how accounts get flagged
      pollJitterSeconds: 6
      sendPerDay: 30
      sendGapSeconds: 8
    life:
      enabled: false           # turn on to give it a life of its own
      tickSeconds: 300
      timezone: Asia/Hong_Kong
    limits: { dmPerDay: 12, postsPerDay: 2, commentsPerDay: 15, replyCooldownSeconds: 20 }

⚠️ A patch replaces the whole row config — there is no deep merge. Defaults live in lib/config.js; the full Instagram setup is in docs/IG-SETUP.md.

🎭 Change the persona

persona/example/ is the bundled character:

persona.md      identity, backstory, wants, fears (the important one)
style.md        how it talks: sentence length, tone, emoji habits
boundaries.md   what it refuses: no money, no secrets, nothing personal to strangers
seed.json       the memories it is born with, in the first person

Copy the folder, edit the words, point personaPath at it — that's somebody else. No code changes.

🛡 Safety, enforced in code

  • Strangers are ignored by default; when enabled they get only short, harmless, non-personal replies
  • Everything outbound is scrubbed of keys, tokens and system prompt shapes
  • Webhook HMAC verification; daily caps, per-thread cooldown, quiet hours, a 24-hour reply window
  • Memory layering: a memory tagged for one person never appears in another's conversation
  • A corrupt memory file is quarantined as .corrupt-<timestamp> — never silently dropped

Full notes →

🧩 The 21 model tools (click)

The model must call a tool to speak — its text output is only thinking. Keeping thought and speech apart is what stops it from turning into a support bot.

ToolWhat it does
ig_send_dmtype (the only way to speak; may be several messages)
voice_sendspeak (TTS or a recorded voice pack)
sticker_list / sticker_send / sticker_collectbrowse, send, collect stickers
reel_watch / reel_shareread a reel; send one (a link is enough)
slang_query / slang_submitlook up new slang, submit it for approval
people_note / people_listremember specifics about someone
image_describelook at an image (needs a vision endpoint)
ig_inbox / ig_wait / ig_mark_readread, wait for them to finish, wrap up
ig_post / ig_reply_comment / ig_life_statuspublish, reply to comments, inspect itself
memory_remember / memory_recall / memory_forgetmemory

The owner can also send /pause /resume /sleep /status /slang /people /voice /help over DM.

📚 Deep dives

DocumentContents
docs/ARCHITECTURE.mdwhy a plugin instead of an external bridge; the two reply engines; graceful degradation
docs/MEMORY.mdthe four tiers, retrieval scoring, sleep compression, tuning
docs/IG-SETUP.mdMeta app, webhook, voice/sticker/reel setup, the full error-code table
docs/UNOFFICIAL.mdthe personal-account path: ban risk, sidecar setup, session reuse, error codes
docs/PRIVACY.mdwhat is stored, what never leaves, anti-ban mechanics, risk register
CHANGELOG.mdwhat changed in each version, and why

✅ Verify

node scripts/test.mjs          # 199 tests, 0 failures, no dependencies, no network
node scripts/self-check.mjs    # end-to-end dry run with a scripted brain
node scripts/loader-smoke.mjs  # boots under the real Cordis runtime
python src/test_ig_bridge.py   # the unofficial sidecar: stdlib only, no instagrapi needed

⚠️ Honest limitations

  • The official Instagram API is 1:1 only — no groups, no stickers, no forwards
  • The unofficial private API is included now, as a separate opt-in transport (transport: unofficial). It violates Instagram's terms, the account can be banned, and the private protocol changes without notice — nothing about it is guaranteed to keep working. Use an account you can lose; see docs/UNOFFICIAL.md
  • Instagram has no read-back API, so a voice message's verified only means "the send request succeeded"; the protocol requires falling back to text on failure
  • The agent engine has not yet been validated over a long-lived real DSH session; it degrades to the llm engine
  • Not included yet: understanding incoming voice notes, image generation, multi-platform sync

🙏 Credits

surfcore — a complete "DeepSeek raised on QQ" implementation. Several hard-won decisions here (the model must speak through tools, failures carry code + hint, persona and safety kept apart, budgets enforced in code) came from reading its source and docs.

License

MIT

관련 플러그인