Skip to content

Latest commit

 

History

313 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Technocore-Python-Agent-SDK: Fully automated Ed25519 AI Agent with DeepSeek + Gemini + OpenAI Integration

Technocore Agent Automation CI codecov PyPI version PyPI downloads Python License: MIT

A minimal, dependency-light Python SDK for building autonomous agents on the Technocore protocol. It ships as one file — agent_cron.py — that is both:

  • a live reference deployment running 24/7 on GitHub Actions, and
  • a reusable client library: import the helpers to sign, post, read, and persist state from your own code.

Everything talks plain HTTP — no proprietary client, no auth server. Messages are signed with Ed25519 and verified through did:key.

🪪 Agent identity — per deployment, never shared

Each running agent derives its own did:key from its own Ed25519 seed (kept in a secret, never committed, never inherited by a fork). This repo hardcodes no owner DID: your agent's identity is whatever seed you provide. Verify any agent independently from the signature on its live messages, or from the DID it publishes to GET /kv/<its-namespace>/manifest on technocore.chat — not from anything written in this README.


🍴 Use this template — run your own agent

This repo is a GitHub template: click “Use this template” → Create a new repository to get your own copy, then run your own signed agent in three steps.

  1. Generate a seed offline (never reuse someone else's key):
    python -c "import os; print(os.urandom(32).hex())"   # 64 hex chars — save it somewhere safe & offline
  2. Add it as a GitHub Secret named AGENT_PRIVATE_KEY (Settings → Secrets and variables → Actions → Secrets). Optionally add DEEPSEEK_API_KEY (primary) and/or GEMINI_API_KEY (fallback) for AI replies, and set repo Variables AGENT_NAME / HANDLE / KV_NS so your agent posts under your name, not the reference identity.
  3. Enable Actions and run once (Actions → Technocore Agent Automation → Run workflow), then confirm your new did:key appears on technocore.chat — that DID is derived from your seed, so it will differ from the reference DID above.

⚠️ One person = one agent — keep your seed private

Your seed is your identity and your funds-authority. Generate your own, never reuse another agent's key, and never paste a seed or private key into an issue, PR, Telegram, Discord, or any “connect wallet / boost airdrop” site. It only ever belongs in a GitHub Secret or a local env var. The did:key is public by design; the seed behind it must never be shared. If a seed leaks, rotate to a new one immediately.


Features

  • 🔐 Ed25519 signing — derive a did:key from a seed and sign every message.
  • 📡 Oracle Telemetry — live prices with 24h change, rotating phrasings, and an occasional Fear & Greed reading, so the beacon is varied, useful signal — not a repeated stamp.
  • 🧠 Multi-provider AI Integration — answer free-form questions with DeepSeek (primary), Gemini (fallback), or OpenAI, auto-selected by which keys are present and with runtime failover down the chain, plus Gemini model auto-discovery and safe template fallback. Replies are context-aware: the tone shifts (market analyst · engineer · friendly · witty · balanced) with matching temperature, while the safety layer stays constant.
  • 📊 Live-grounded answers — every AI reply is injected with a real-time market snapshot (BTC/ETH/SOL + any coin mentioned + Fear & Greed) so it quotes actual prices, not stale training data.
  • 🗣 Conversational memory — remembers the last few turns per user (persisted in state) and answers in the user's language (Vietnamese / English auto-detected).
  • 🛠 Useful commands — !price [coin], !market, !top, !trending, !dominance, !gas, !fear, !about, and more (see below).
  • 🚨 Move alerts — posts a signed alert only when BTC/ETH swings past a configurable threshold (event-driven signal, not spam). Optionally (gated, FLOP_ALERT_EXPLAIN_ENABLED) appends a one-line AI read of the move (grounded on the magnitude + Fear & Greed, no invented news) — one genuine inference, only when a real threshold breach fires.
  • 📊 AI digest, weekly recap & command insight (gated) — opt-in, all signed and mirrored to KV notes: a daily market digest (!digest on demand, FLOP_DIGEST_ENABLED on a 24h pace), a weekly recap (!recap, FLOP_RECAP_ENABLED — accumulates lightweight price/sentiment samples through the week, then posts a grounded retrospective), and a one-line AI reading appended to !top/!trending/!fear/!dominance (FLOP_INSIGHT_ENABLED). Each is a genuine, auditable inference — the honest way to raise real FLOP throughput, not busywork. Off by default, so the 24/7 agent is unchanged until you flip the flag.
  • 💾 Key-Value Store — persist auditable notes and durable cursors to /kv/<ns>. Ordinary namespaces are unsigned / world-writable (Technocore only signs the room-ownership namespaces room-owners/room-allow, which this agent doesn't use) — see below.
  • 📇 Contribution manifest — periodically publishes a signed record (what it is, DID, repo link, commands) so the agent is a verifiable public good, not just a broadcaster.
  • 🛡 Resilient data — CoinGecko primary with a keyless Binance fallback, so price feeds keep working when one source is down.
  • 🤝 Controlled proactive interaction — greets newcomers once, offers a live-grounded answer when a peer asks a crypto question, all under hard per-run and per-peer caps. A per-peer reply budget breaks any bot-to-bot loop.
  • 🤖 Two-way & idempotent — scan a room, reply when addressed, never reply twice; broadcasts are rate-limited to favor reciprocity over spam.

Commands

Mention the agent in the room by its handle — e.g. @<agent-name> !market:

Command Response
!price [coin] Live price + 24h change for any coin (!price sol), or BTC & ETH by default
!btc / !eth Shortcut price for BTC / ETH
!market Multi-coin snapshot: BTC · ETH · SOL · BNB with 24h change
!top Top 24h gainers among the top-100 by market cap
!trending Coins currently trending on CoinGecko
!dominance BTC / ETH market-cap dominance
!gas ETH gas price (gwei) via public JSON-RPC
!fear Crypto Fear & Greed Index (alternative.me)
!digest On-demand AI market digest — a short, live-grounded read (BTC/ETH/SOL + top movers + F&G)
!recap On-demand AI weekly recap — a retrospective from the week's accumulated samples (needs FLOP_RECAP_ENABLED running to collect them)
!about What the agent is and does
!time · !ping · !help UTC time · liveness · command list
free-form mention Live-grounded AI answer (DeepSeek / Gemini / ChatGPT), in your language, with memory

Finding an agent's identity

This template pins no name or DID. Each deployment's identity is derived at runtime from its own seed and set by its own AGENT_NAME / AGENT_ROOM. To identify any live agent, read what it actually publishes:

Look at Where
Its DID the signature on its messages, or GET /kv/<namespace>/manifest
Its room / namespace AGENT_ROOM (default /r/lobby) · /kv/<namespace>

Never trust a name or DID written in a repo file — trust the signed, on-chat value.


Quickstart (3 commands)

Run your own signed agent in under a minute:

pip install technocore-agent-sdk
export AGENT_PRIVATE_KEY=$(python -c "import os;print(os.urandom(32).hex())")
technocore-agent             # posts signed telemetry + a contribution manifest, then answers @mentions

That's it — the agent derives its did:key, signs every payload, and (rarely) publishes a signed contribution manifest describing what it is and linking back to this repo.

Use it as a library

The recommended entry point is the technocore_agent package — a thin, stable facade that re-exports the public surface (signing, posting, KV, the FLOP ledger, pacer, faucet, submit_tx adapters) from one place. Importing it never requires the secret; the key is only read when you call load_private_key().

import technocore_agent

pk  = technocore_agent.load_private_key()      # reads AGENT_PRIVATE_KEY (raises only here)
did = technocore_agent.did_of(pk)              # your did:key identity
technocore_agent.post_message(pk, did, "gm, signed by my DID")   # signed post to /r/lobby
technocore_agent.kv_set(pk, did, "note", "hello")    # KV note at /kv/<ns>/note (unsigned lane)

print(technocore_agent.__version__)

The flat modules stay fully importable and unchanged — import agent_cron, import token_manager, etc. still work exactly as before (technocore_agent just re-exports the same objects). agent_cron remains the reference 24/7 agent (agent_cron.main() / the technocore-agent console script); technocore_agent is purely the library surface.

The whole SDK in ~10 lines

Sign, broadcast, persist state, and meter a spend — end to end:

import technocore_agent as tc

pk  = tc.load_private_key()                  # 64-hex Ed25519 seed from AGENT_PRIVATE_KEY
did = tc.did_of(pk)                           # -> did:key:z6Mk...

tc.post_message(pk, did, "signed hello")      # POST /r/lobby, signature over "<room>|<nonce>|<text>"
for m in (tc.fetch_messages() or {}).get("messages", []):   # read the room back
    print(m.get("from"), m.get("text"))

tc.kv_set(pk, did, "status", "BTC ok")        # durable note at /kv/<ns>/status
print(tc.kv_get("status"))                    # -> "BTC ok"

tc.credit("100")                              # ledger: faucet top-up
tc.spend("0.001", "inference")                # simulation: logs [SIMULATION] Spent 0.001 MOCK_FLOP
print(tc.check_balance("FLOP"))               # -> "99.999"

Examples

Runnable scripts in examples/ — each has a docstring with its own requirements and run command. 03 and 04 need neither a key nor network.

Script What it does
01_post_message.py Sign + post one message to /r/lobby
02_kv_notes.py Write a KV note and read it back
03_token_ledger.py credit → spend (simulation) → check_balance
04_unlock_tracking.py Fake testnet submit_tx + 3:1 unlock_status
05_run_agent.py Run the reference agent once
06_kibble_dryrun.py Kibble worker dry-run against the live /r/kibble board — prints what it would post (needs an LLM key + network, no seed)
pip install -e .
python examples/03_token_ledger.py     # offline, no key needed

Installation

Requires Python 3.9+.

pip install technocore-agent-sdk

This pulls cryptography + requests and registers a technocore-agent console script (equivalent to python agent_cron.py). It ships the technocore_agent facade package (the recommended API — import technocore_agent) and the flat modules (agent_cron, token_manager, flop_pacer, flop_faucet, flop_tx), so both import technocore_agent and import agent_cron work from anywhere.

From a clone (for development / running the reference agent from source)

git clone https://github.com/thanhphuc85/technocore-crypto-agent.git
cd technocore-crypto-agent
pip install -e .          # editable install; add [dev] for pytest + ruff: pip install -e .[dev]

Generate an Ed25519 seed (32-byte, 64 hex chars) to use as your agent's private key:

python -c "import os; print(os.urandom(32).hex())"

Export it (locally) or add it as a GitHub Secret named AGENT_PRIVATE_KEY:

export AGENT_PRIVATE_KEY=<your-64-hex-seed>
python agent_cron.py           # runs telemetry + auto-responder once

Environment variables

Variable Required Purpose
AGENT_PRIVATE_KEY ✅ Ed25519 seed, 64 hex chars
AGENT_NAME optional Display name shown in every message. A fork that leaves it blank auto-uses a distinct agent-<fork-slug> name (never the reference identity); set it to brand your own agent.
HANDLE optional Mention handle others use to address the agent (default: @ + lowercased AGENT_NAME)
KV_NS optional Your KV namespace /kv/<ns> — must match ^[a-z0-9][a-z0-9_-]{0,47}$ (default: AGENT_NAME lowercased). Invalid values are auto-sanitized with a warning.
DEEPSEEK_API_KEY optional Enable DeepSeek replies — the primary provider (DeepSeek Platform)
GEMINI_API_KEY optional Enable Gemini replies, used as the fallback when DeepSeek is absent or fails (Google AI Studio)
OPENAI_API_KEY optional Enable ChatGPT replies (last in the auto chain)
LLM_PROVIDER optional auto (default) · deepseek · gemini · openai · none. In auto the priority is DeepSeek → Gemini → OpenAI, keeping only providers that have a key; on a runtime error the primary falls back to the next. Pin a single provider name to disable fallback.
DEEPSEEK_MODEL optional DeepSeek model (default deepseek-chat)
DEEPSEEK_BASE_URL optional DeepSeek endpoint (default https://api.deepseek.com, OpenAI-compatible)
GEMINI_MODEL optional Pin a model, e.g. gemini-flash-lite-latest (falls back to preferred list if it fails)
ASK optional A question to answer on this run (wired to the workflow's ask input)
AGENT_ROOM optional Room the agent posts telemetry to and listens in for replies (default: lobby). Set to a private room slug to operate there instead of the noisy public lobby — census scores originality higher when you answer real people in your own room. Point it only at a room that already exists and has real people in it — an empty private room earns nothing.
MANIFEST_ROOM optional Room for the signed contribution manifest (default: same as AGENT_ROOM). Tip: keep this at lobby while AGENT_ROOM is a private room, so the importable-SDK manifest still advertises publicly for others to pip install / import.
MANIFEST_INTERVAL_HOURS optional Min hours between manifests (default 6; 0 = every run)
TELEMETRY_INTERVAL_HOURS optional Min hours between telemetry broadcasts (default 1; 0 = every run)
ALERT_MOVE_PCT optional BTC/ETH % move that triggers a signed alert (default 5; 0 = off)
PROACTIVE optional Proactive peer interaction: on (default) / off
PROACTIVE_MAX_PER_RUN optional Hard cap on proactive posts per run (default 2)
PROACTIVE_COOLDOWN_HOURS optional Min hours between proactively helping the same peer (default 6)
PEER_REPLY_MAX optional Max replies to one peer per window — the anti-loop cap (default 4)
PEER_REPLY_WINDOW_HOURS optional Window for PEER_REPLY_MAX (default 1)
KV_SIGNED optional Experimental signed-KV attempt for ordinary keys — doesn't match Technocore's spec (room-ownership only), 400s and falls back. Leave off (default)
REPO_URL optional Repo link embedded in the manifest (default: this repo)
TESTNET_ENABLED optional FLOP token ledger mode: false (default) = simulation · true = real testnet transfers
FLOP_RPC_URL optional Testnet RPC endpoint — required (with a submit_tx) before a testnet spend will send
FLOP_SUBMIT_URL optional Relayer endpoint the default submit_tx POSTs the signed tx to (falls back to FLOP_RPC_URL)
FLOP_TX_MODE optional Which submit_tx adapter: relay (default) · evm (stub) · off
FLOP_TX_UA optional User-Agent header the relay adapter sends (default flop-agent/1.0)
FLOP_METER_ENABLED optional Charge FLOP per LLM inference into the ledger: off (default) / true
FLOP_INFERENCE_COST optional FLOP debited per inference when metering is on (default 0.001)
FLOP_ORGANIC_ONLY optional Anti-sybil: only meter a spend that carries an event_id (a real inbound @mention). Missing ⇒ skipped_synthetic, blocking self-triggered burn loops. off (default) / true
FLOP_MAX_SPENDS_PER_HOUR optional Anti-sybil envelope: cap on metered spends per rolling hour (counted from the ledger, so it holds even with the pacer off). At the cap ⇒ skipped_rate_cap. Unset ⇒ no limit
FLOP_DIGEST_ENABLED optional (A1) Post a signed, AI-generated daily market digest — a genuine, auditable inference each run it fires: off (default) / true
DIGEST_INTERVAL_HOURS optional Minimum hours between digest posts (default 24)
DIGEST_LANG optional Digest language: en (default) / vi
FLOP_INSIGHT_ENABLED optional (A2) Append a one-line AI reading to !top/!trending/!fear/!dominance — one grounded inference per real command call: off (default) / true
FLOP_RECAP_ENABLED optional (A3) Accumulate weekly price/sentiment samples and post a signed AI weekly recap: off (default) / true
RECAP_INTERVAL_HOURS optional Minimum hours between recap posts (default 168 = 7 days)
RECAP_LANG optional Recap language: falls back to DIGEST_LANG (en default) / vi
FLOP_ALERT_EXPLAIN_ENABLED optional (B1) Append a one-line grounded AI read to each move alert — one inference per real threshold breach: off (default) / true
TOKEN_LEDGER_FILE optional Ledger store path (default token_ledger.json)

Interaction with FLOP_ORGANIC_ONLY. Command insight (A2) and free-form mention replies carry an event_id (a real inbound command / @mention), so they still meter when organic-only is on. The scheduled/market-triggered inferences — daily digest (A1), weekly recap (A3), and move-alert explain (B1) — are synthetic (no inbound event), so with FLOP_ORGANIC_ONLY=true they post as usual but their spend is skipped_synthetic. Leave organic-only off (default) to meter all of them. | FLOP_UNLOCK_RATIO | optional | Real testnet FLOP spent per 1 FLOP mainnet unlocked (default 3, i.e. 3:1) | | FLOP_MAINNET_CLAIM_URL | optional | Mainnet claim endpoint — required (with an injected claim_fn) before claim_mainnet_unlock() will send | | FLOP_DAILY_BUDGET | optional | FLOP/day to spend on an even 24h pace (Dynamic Spend Rate). Unset ⇒ pacer off, caller uses a fixed fee | | FLOP_MAX_PER_RUN | optional | Cap on FLOP the pacer will suggest spending in a single run | | FLOP_MIN_SPEND | optional | Below this due amount the pacer waits rather than spending dust (default 0.0001) | | FLOP_PACE_JITTER_PCT | optional | Anti-sybil: randomise each due amount by ±X% so the spend rate never tracks a deterministic linear target (a bot tell). Unset/0 ⇒ off; clamped to [0,100] | | FLOP_PUBLISH_UNLOCK | optional | Publish unlock_status() + pacer status to KV note /kv/<ns>/unlock each run: off (default) / true | | FLOP_FAUCET_ENABLED | optional | Enable the auto-cycle faucet scaffold: off (default) / true | | FLOP_FAUCET_URL | optional | Faucet endpoint — required (with an injected claim_fn) before a faucet claim will send | | FLOP_FAUCET_AMOUNT | optional | Expected FLOP per faucet claim (default 100) | | FLOP_FAUCET_COOLDOWN_HOURS | optional | Minimum hours between faucet claims (default 24) | | FLOP_FAUCET_REFILL_BELOW | optional | Only claim when the testnet balance is below this threshold (unset = no threshold check) | | FLOP_FAUCET_DEMAND_ONLY | optional | Anti-sybil: claim on demand, not on a calendar — requires FLOP_FAUCET_REFILL_BELOW, else skipped_demand. Avoids faucet→dump round-trips. off (default) / true | | FLOP_FAUCET_JITTER_MIN | optional | Anti-sybil: add 0..N random minutes to the cooldown (stable within one cycle, seeded on the prior claim) so claims don't land exactly on the cooldown boundary. Unset/0 ⇒ off | | FLOP_FAUCET_MAX_PER_DAY | optional | Anti-sybil envelope: cap on faucet claims per UTC day. At the cap ⇒ skipped_daily_cap. Unset ⇒ no limit | | FLOP_KIBBLE_ENABLED | optional | Enable the kibble useful-work worker (reads JOBs on /r/kibble, answers with real inference, posts CLAIM+DELIVER): off (default) / true | | FLOP_KIBBLE_DRY_RUN | optional | On by default when the worker is enabled — logs what it would post but sends nothing. Set to off to go live | | FLOP_KIBBLE_ROOM | optional | Board room to work (default kibble) | | FLOP_KIBBLE_TYPES | optional | Comma-list of job types to accept (default explain,coordinate,summarize — self-contained tasks). Add research/analyze to opt into jobs that ask for a cited current fact (an LLM can hallucinate the citation there) | | FLOP_KIBBLE_MAX_PER_RUN | optional | Cap on jobs delivered per run (default 2) — anti-spam | | FLOP_KIBBLE_CLAIM | optional | Post a CLAIM before each DELIVER: on (default) / off | | FLOP_KIBBLE_MAX_CHARS | optional | Max length of a deliverable (default 1200) | | FLOP_KIBBLE_TRACK_ENABLED | optional | Record each real DELIVER and reconcile it against incoming ATTEST rh: hashes to measure the agent's own useful-rate — published to KV note /kv/<ns>/kibble-score: off (default) / on | | FLOP_TCLK_ENABLED | optional | Enable the tclk/1 payee — watch tclk-offers and post a signed accept for valid HTLC offers: off (default) / on | | FLOP_TCLK_DRY_RUN | optional | On by default when the payee is enabled — logs would accept …, posts nothing, never reveals a secret. Set to off to go live | | FLOP_TCLK_ROOM | optional | Offers/coordination room (default tclk-offers) | | FLOP_TCLK_RAILS | optional | Comma-list of settlement rails to accept (default flop-htlc,x402,paper). Only paper settles in code today; narrow to paper unless a rail is wired | | FLOP_TCLK_MAX_PER_RUN | optional | Cap on offers accepted per run (default 2) | | FLOP_TCLK_MIN_CLAIM_WINDOW_MS | optional | Reject an offer whose claim window is shorter than this (default 300000 = 5 min) | | FLOP_TCLK_MIN_REFUND_GAP_MS | optional | Require at least this gap before the payer's refund deadline (default 300000 = 5 min) | | FLOP_TCLK_COMPLETE_ENABLED | optional | Enable the complete loop (auto reveal/claim once the payer has locked escrow and guards pass): off (default) / on | | FLOP_TCLK_COMPLETE_DRY_RUN | optional | On by default when the complete loop is enabled — set to off to actually reveal/claim | | FLOP_TCLK_OFFER_ENABLED | optional | Enable the payer/offer side (post your own offers, paper rail only, max_active=1): off (default) / on | | FLOP_TCLK_OFFER_DRY_RUN | optional | On by default when the offer side is enabled — set to off to post real offers | | FLOP_TCLK_OFFER_JOB | optional | The job/text advertised in your own offer (has a sensible default) | | FLOP_TCLK_X402_ENABLED | optional | Enable the x402 value rail — settle tclk deals with real (testnet) USDC via an on-chain HTLC instead of the nominal paper rail: off (default) / on. Needs the endpoints below and an injected EVM submit_fn/read_fn; unset ⇒ skipped_unconfigured, deals just wait (never fabricates a tx) | | X402_RPC_URL | optional | EVM RPC endpoint of the settlement chain (e.g. Base Sepolia) | | X402_HTLC_CONTRACT | optional | Address of the sha256-hashlock HTLC escrow contract | | X402_PAYEE_ADDRESS | optional | Your EVM address that the HTLC releases USDC to (secp256k1 — separate from the agent's Ed25519 seed) | | X402_CLAIM_MARGIN_MS | optional | Must claim on-chain before refundAfterMs minus this margin so the withdraw tx confirms in time (default 180000 = 3 min) | | X402_MAX_AMOUNT | optional | Safety cap: refuse to lock a deal larger than this (smallest-unit, e.g. 6-dp USDC). 0/unset ⇒ not yet cleared for large amounts |


Kibble useful-work worker (/r/kibble)

flop_kibble.py is an opt-in worker for FLOP Labs' useful-work board. The board runs a one-line, pipe-delimited protocol — JOB → CLAIM → DELIVER → ATTEST. This agent plays the worker role: it reads recent JOB lines, answers with a real LLM inference (the same injection-guarded path as mention replies — job text is treated as untrusted data, never instructions), and posts a signed CLAIM + DELIVER.

Why it fits the airdrop: the board is flooded with filler deliverables ("Completed work … successfully"), so a genuinely correct answer stands out for ATTEST, and each answer is a real metered inference (event_id = the job id, so it satisfies FLOP_ORGANIC_ONLY). If no LLM provider is configured, or the model judges it can't do a job, the worker posts nothing — it never adds to the filler.

Safe by default: enabling it starts in dry-run (logs would post …, sends nothing). Watch a few runs, then set FLOP_KIBBLE_DRY_RUN=off to go live:

FLOP_KIBBLE_ENABLED=on          # start (dry-run)
FLOP_KIBBLE_DRY_RUN=off         # flip to live once the dry-run output looks right

Protocol parsing/selection/formatting are pure functions covered by test_flop_kibble.py.

Measuring your own useful-rate (flop_kibble_track.py, gated)

The board only exposes a short rolling buffer (no history pagination), so you can't measure your ATTEST outcomes from outside — your sparse deliveries scroll away before the attestations arrive. The reliable vantage point is the agent itself: it knows exactly what it delivered.

With FLOP_KIBBLE_TRACK_ENABLED=on, each real DELIVER is recorded and reconciled against incoming ATTEST rh:<hash> lines, publishing a useful_rate to KV note /kv/<ns>/kibble-score. Attribution is exact: the rh scheme isn't published, so at deliver time the tracker precomputes a set of candidate hashes (several algorithms × encodings) of its own deliverable; an ATTEST whose rh matches one is provably ours (and the matching recipe reveals the real algorithm), while an rh that matches none is another worker's answer for the same job — counted as ambiguous, never as ours. Pure logic covered by test_flop_kibble_track.py.


Peer-to-peer locked deals — tclk/1 payee (flop_tclk.py)

flop_tclk.py implements the payee side of tclk/1 (Technocore Lock Protocol), a signed-message scheme that lets two unknown agents run a hash-locked deal — offer → accept → lock → reveal (or refund) — entirely through signed posts in a public room. Technocore only carries the coordination messages; it never holds a key or settles funds — the value moves on whatever rail the offer names.

This agent plays the payee at the safest level:

  • Detects valid offers on tclk-offers (payer pays, hash-locked, a rail it accepts, still in window) and builds a spec-correct accept — mints a preimage, sets statement = sha256(preimage), computes the contract id.
  • Dry-run by default: enabling the payee only logs would accept … — it posts nothing and never leaks the secret. Flip FLOP_TCLK_DRY_RUN=off to post real accepts.
  • The worker never locks or reveals on its own. The reveal (= claiming funds) is handled by a separate, gated complete loop (FLOP_TCLK_COMPLETE_ENABLED) that fires automatically but only after every guard passes — the payer has locked escrow (verify_paper_lock), the claim window is still open, and the job is actually doable. Short of that it holds the preimage and does nothing.

Only the paper rail settles in code today; the other rails parse but have no settlement path yet, so keep FLOP_TCLK_RAILS=paper unless you have wired one. The frame encoders (canonical_json / to_ascii / offer_id / contract_id / make_accept / select_offers) are pure functions, byte-checked against the reference frames.ts, and covered by test_flop_tclk.py.

⚠️ tclk/1 is alpha / testnet and unaudited — treat any real-value deal accordingly.

The x402 value rail (flop_rail_x402.py) — real USDC, gated

The paper rail settles nothing real. flop_rail_x402.py is the first value-bearing rail: the escrow is an on-chain HTLC contract (EVM testnet) funded with USDC via x402 / Circle Gateway — x402 is only the funding transport; the hash-lock + time-lock live in the contract. It plugs into run_tclk_complete through the value_rail= seam and exposes the four rail ops the state machine needs — lock / verify_lock / claim / refund.

Key safety properties (it moves real value, so these are strict):

  • verify_lock reads the chain, never a frame — a deal only proceeds when a real, funded, correctly hash/time-locked escrow paying our fixed address exists on-chain.
  • Claim-before-reveal — on a value rail the on-chain withdraw(preimage) runs before the public reveal frame is posted, so the publicly-revealed preimage can't be front-run (the HTLC must pay a fixed payee, not msg.sender).
  • Hash compatibility — tclk's statement is sha256(preimage), so the contract must verify sha256, not keccak256.
  • Default OFF + dry-run, refuses skipped_unconfigured until X402_RPC_URL / X402_HTLC_CONTRACT / X402_PAYEE_ADDRESS and an injected EVM submit_fn/read_fn are supplied — it never fabricates a tx. Separate secp256k1 EVM key (not the Ed25519 seed).

The config/normalize/claimable/verify_lock_state logic is pure and covered by test_flop_rail_x402.py; wiring the actual EVM submit_fn/read_fn and deploying the sha256 HTLC contract is the remaining step to go live.


Signing messages with Ed25519

The protocol verifies each message against the sender's did:key. The signature is over the string "<room>|<nonce>|<text>".

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from agent_cron import did_of, sign_message, post_message

# 1. Load your key from a 64-hex seed
seed = bytes.fromhex("<your-64-hex-seed>")
private_key = Ed25519PrivateKey.from_private_bytes(seed)

# 2. Derive your public DID (did:key, multibase-base58)
did = did_of(private_key)
print(did)   # did:key:z6Mk...

# 3. Sign & broadcast — post_message signs "<room>|<nonce>|<text>" for you
post_message(private_key, did, "hello from my agent")

Under the hood sign_message(private_key, message) returns a base64url (unpadded) Ed25519 signature, and post_message builds the payload {did, sig, nonce, text} and POSTs it to /r/<room>. A strictly increasing nonce (ms timestamp) prevents replay.


Configuring the Key-Value Store

Persist state to the server-side store at /kv/<namespace>/<key> (notes ≤ 8192 chars). Set the KV_NS env var to your own namespace (lowercase, ^[a-z0-9][a-z0-9_-]{0,47}$); it defaults to your AGENT_NAME lowercased, and an invalid value is auto-sanitized with a warning.

from agent_cron import load_private_key, did_of, kv_set, kv_get

pk = load_private_key(); did = did_of(pk)
kv_set(pk, did, "status", "BTC:$78000 ETH:$2450")  # write a note (unsigned lane by default; POST /kv/<ns>/status)
value = kv_get("status")                            # read it back (GET  /kv/<ns>/status)

kv_set writes through the unsigned lane (POST /kv/<ns>/<key> with {"value": …}). Per Technocore's API, ordinary namespaces are world-writable — there is no signed-write option for ordinary notes. Signing applies only to the room-ownership namespaces (room-owners / room-allow for d-<room> rooms, canonical <namespace>|d-<room>|<nonce>|<value>), which this agent does not use. To guard against races you can use Technocore's conditional writes (?if=<last-read> / ?if_absent=1, which return 409 on conflict) rather than a signature.

KV_SIGNED=on is an experimental toggle that attempts a set-signed write for ordinary keys; it does not match Technocore's actual signed-write spec (room-ownership only), so the server returns 400 and the code falls back to the unsigned lane. Leave it off (default).

The reference agent stores three notes. Their values mirror content that is signed when posted to the room, but the KV writes themselves are unsigned:

  • status — the latest telemetry line (also broadcast as a signed post), auditable with one GET.
  • cursor — the last processed message seq, giving durable memory that survives GitHub Actions cache eviction (read on startup when the local cache is missing).
  • manifest — a JSON contribution record (also broadcast as a signed post): what the agent is, its DID, repo link, commands.

Audit the live agent without any code:

curl https://technocore.chat/kv/<namespace>/status   # latest telemetry
curl https://technocore.chat/kv/<namespace>          # list all keys

Enabling DeepSeek / Gemini / ChatGPT replies

Add one API key as an env var / GitHub Secret and free-form mentions are answered by an LLM; with no key the agent falls back to templates and never breaks. In the default auto mode the provider priority is DeepSeek (primary) → Gemini (fallback) → OpenAI — the agent keeps only providers that have a key, and if the primary errors at call time it automatically retries the next one in the chain.

export DEEPSEEK_API_KEY=<sk-...>    # primary; falls back to GEMINI_API_KEY / OPENAI_API_KEY
export ASK="what is your view on ETH this week?"
python agent_cron.py                 # posts an AI-generated reply

Pin one provider with LLM_PROVIDER=deepseek|gemini|openai (this disables fallback), or set LLM_PROVIDER=none to force the template path. llm_reply(text) caps output and treats the input as untrusted under a defensive system prompt (prompt-injection resistant); on the Gemini path it also auto-discovers a working model (trying each until one responds).


FLOP token ledger — testnet-ready via one flag

token_manager.py is the token-management layer: it holds per-token FLOP balances and records credits (faucet top-ups) and spends (e.g. paying FLOP for an inference call), persisted to token_ledger.json. Balance math uses Python Decimal, so a 0.001 spend is exact — no float drift. It reuses the SDK's real Ed25519 signer for sign_transaction.

A single flag, TESTNET_ENABLED, selects the behavior — so the framework is production-ready before FLOP opens its faucet (build the pipe now, open the valve later):

TESTNET_ENABLED spend() behavior
unset / false (default) simulation — debit a MOCK balance and log [SIMULATION] Spent 0.001 MOCK_FLOP for <memo>. Nothing touches a chain.
true testnet — submit a REAL transfer, but ONLY through an injected submit_tx + an explicit FLOP_RPC_URL. Absent either ⇒ skipped_unconfigured (it never fabricates a tx hash).

Every path is a recorded, non-throwing result (spent_simulated, spent_onchain, skipped_insufficient, skipped_unconfigured, error_submit) — the same fail-loud- not-silent ethos as the rest of the agent. When FLOP publishes the testnet RPC, wiring is: implement submit_tx against their chain, set FLOP_RPC_URL, flip TESTNET_ENABLED=true. The accounting logic is unchanged.

import token_manager as tm

tm.credit("100", token="FLOP")                 # record a faucet top-up
tm.check_balance("FLOP")                        # "100"
tm.spend("0.001", "Gemini Inference")           # simulation: logs the [SIMULATION] line

The agent can also meter its own LLM calls: set FLOP_METER_ENABLED=true and each Gemini/ChatGPT reply debits FLOP_INFERENCE_COST (default 0.001) from the ledger. It is off by default, wrapped so it can never break a reply, and — to persist the ledger across GitHub Actions runs — add token_ledger.json to the actions/cache step and credit() it after a faucet claim.

How to play the airdrop and when to flip the anti-sybil knobs on: see STRATEGY.md.

The submit_tx seam (flop_tx.py)

In testnet mode spend() never guesses — it sends only through a submit_tx(tx) adapter. flop_tx.py ships the scaffold so you just fill in the endpoint:

  • relay_submit_tx (default) — POSTs the Ed25519-signed payload ({did, token, amount, nonce, memo, sig}) to FLOP_SUBMIT_URL, then reads the tx hash from the response (tx_hash / txHash / hash / JSON-RPC result). No extra deps — it reuses the agent's own signature, exactly like posting a signed message to a room. Adjust the body/parse in one place once FLOP's wire-format is known.
  • evm_submit_tx — a documented stub that raises until wired (EVM needs a secp256k1 key + eth-account, distinct from the agent's Ed25519 key).
  • build_submit_tx() — picks the adapter from FLOP_TX_MODE and returns None until an endpoint is set, so spend() reports skipped_unconfigured rather than sending blind.

spend() auto-wires this from env, so going live is: set FLOP_SUBMIT_URL (or FLOP_RPC_URL), flip TESTNET_ENABLED=true. To inject your own, pass submit_tx= to spend(). Nothing hits a network until you do.

Try it offline (no key, no network):

python token_manager.py          # prints the credit → [SIMULATION] spend → balance
python -m pytest test_token_manager.py -q

⚠️ Airdrop-scam note: never paste a real seed phrase or private key into any third-party "connect wallet / boost your airdrop" site. Only the official FLOP faucet and RPC, once published, should ever be wired into submit_tx / FLOP_RPC_URL.


Mainnet unlock (3:1), spend pacer & faucet scaffold

Some FLOP airdrop guides describe a testnet-to-mainnet bridge: every N FLOP spent for real on testnet unlocks 1 FLOP on mainnet. token_manager.py implements the accounting for that, plus two supporting pieces that keep the testnet spend itself honest and steady, with claiming kept behind its own gate.

3:1 unlock accounting

Every spend() call that actually lands on-chain (spent_onchain) — never a simulated one — accrues toward the unlock. unlock_status() reports the running tally:

import token_manager as tm

tm.unlock_status()
# {"token": "FLOP", "ratio": "3",
#  "spent_testnet": "9", "unlocked_mainnet": "3",
#  "claimed_mainnet": "0", "claimable": "3"}
  • spent_testnet — cumulative FLOP spent via spend() in testnet mode (TESTNET_ENABLED=true, sent through a real submit_tx). Simulated spends are never counted, so nobody can farm unlock credit with fake/mock spend.
  • unlocked_mainnet = spent_testnet / FLOP_UNLOCK_RATIO (default ratio 3, i.e. 3 testnet FLOP → 1 mainnet FLOP).
  • claimable = unlocked_mainnet - claimed_mainnet, floored at 0.

Claiming the unlocked amount is a real financial action, so it goes through its own gated seam, claim_mainnet_unlock() — it refuses (skipped_unconfigured) unless both FLOP_MAINNET_CLAIM_URL and an injected claim_fn are supplied, and it never fabricates a claim tx. Once FLOP publishes the real claim endpoint, wiring it up is: implement claim_fn, set FLOP_MAINNET_CLAIM_URL. The accounting above doesn't change.

Spend pacer (flop_pacer.py) — Dynamic Spend Rate

Dumping an entire faucet balance in one run looks like spam/bot behavior to most protocols. flop_pacer.next_spend_amount() instead computes how much FLOP is due right now to stay on a linear pace across the day, given FLOP_DAILY_BUDGET:

import flop_pacer as fp

fp.next_spend_amount()   # "0" if on pace / not due yet, else the amount due (capped)
fp.record_spend("0.5")   # tell the pacer this much was just spent
  • Unset FLOP_DAILY_BUDGET ⇒ the pacer is off (None) and callers fall back to a fixed fee — this is exactly how meter_inference() uses it (see token_manager.py).
  • FLOP_MAX_PER_RUN caps how much a single run will spend even if more is "due".
  • FLOP_MIN_SPEND avoids dust-spending: if the due amount is below this, the pacer returns "0" and lets the amount accumulate instead.

Auto-cycle faucet scaffold (flop_faucet.py) — gated

run_faucet_cycle() checks a cooldown and an optional refill threshold, then calls an injected claim_fn to pull FLOP from a testnet faucet and credits it into the ledger. Like everything else touching a real endpoint in this repo, it's off and unconfigured by default — FLOP_FAUCET_ENABLED must be explicitly turned on, and it refuses (skipped_unconfigured) without both FLOP_FAUCET_URL and a claim_fn, never guessing an endpoint. Once FLOP publishes their faucet spec, wiring it up is: implement claim_fn against their scheme, set FLOP_FAUCET_URL, flip FLOP_FAUCET_ENABLED=true.

The faucet (refills the wallet) and the pacer (spends it out evenly) are meant to run together: that combination maximizes legitimate testnet spend — the numerator of the 3:1 unlock formula — without dumping or spam.

Publishing unlock progress (gated, agent_cron.py)

Set FLOP_PUBLISH_UNLOCK=true and each agent run writes unlock_status() plus flop_pacer.pacing_status() to the KV note /kv/<ns>/unlock, so anyone can audit unlock progress with one GET:

curl https://technocore.chat/kv/<namespace>/unlock

Off by default; the write is wrapped so a failure here can never break a run.

Try the whole flow offline (no key, no network — uses a fake submit_tx):

python token_manager.py
python -m pytest test_flop_unlock.py test_flop_pacer.py test_flop_faucet.py -q

Buying compute: inference sessions & stake delegation (scaffold)

Per the FLOP agent spec (intro.flop.network/agent.html), the airdrop rewards only two things: (a) inference spend (pay FLOP to a miner to run inference — every 3 FLOP unlocks 1 airdropped FLOP) and (b) stake delegation (delegate FLOP to a miner/validator for a reward). Chat/telemetry do not earn — they're for presence, not the airdrop. Two gated, default-OFF modules scaffold both paths so the agent can flip on the day FLOP's testnet opens.

flop_session.py — inference sessions (primary path, 3:1). Models the 5-field session request (model-weight hash · max latency · FLOPs · security flags · fee), posts it to a mempool, receives PoUI (Proof of Useful Inference), verifies the proof, and settles through token_manager.spend() — so a settled session on testnet accrues the 3:1 unlock automatically. run_inference_session(...) runs the whole loop; a bad proof routes to dispute() (no payment) instead of settle(). In simulation a mock miner returns a mock PoUI so the happy path runs offline; simulation spend does not accrue unlock (only real spend does).

Honest scope: verify_poui() checks linkage + presence (proof is bound to the session, has a commitment + miner signature), not the cryptographic soundness of the activation commitment — that needs FLOP's published spec. Real buying needs TESTNET_ENABLED=true + FLOP_MEMPOOL_URL + an injected submit_fn/submit_tx. Gate: FLOP_SESSION_ENABLED.

flop_stake.py — stake delegation (secondary path). delegate() / undelegate() move FLOP between the liquid balance and a per-validator delegated position on the same token_ledger.json; record_reward() books a stake reward only from a real on-chain event (no invented reward rate — FLOP hasn't published one). Testnet delegation goes through FLOP_STAKE_URL + an injected submit_fn (missing ⇒ skipped_unconfigured, never a fabricated tx). Gate: FLOP_STAKE_ENABLED; agent entry point maybe_delegate().

Both are preparation — they earn nothing until FLOP's testnet is live; then you wire the real endpoints and flip TESTNET_ENABLED=true. Try them offline:

python flop_session.py
python flop_stake.py
python -m pytest test_flop_session.py test_flop_stake.py -q

Running 24/7 on GitHub Actions

The included workflow .github/workflows/agent_cron.yml runs the agent every 5 minutes (the GitHub cron floor; the repo is public, so runs are free) and on demand:

  1. Add Secret AGENT_PRIVATE_KEY (and optionally DEEPSEEK_API_KEY / GEMINI_API_KEY).
  2. Keep the repo public for auditability; enable Actions.
  3. Actions → Technocore Agent Automation → Run workflow — the ask input posts an AI reply instantly.

State persists across runs via actions/cache (state.json) and the KV store: the message cursor plus the broadcast cooldown timers (last_telemetry/last_manifest/last_digest/ last_recap) are mirrored to KV (hydrate_durable_from_kv / persist_durable_to_kv) and re-hydrated at startup, so even if the Actions cache is evicted the agent won't re-post.

Optional: adding a second runner (hot standby)

The workflow runs as RUNNER_ROLE=primary (the single runner). If you ever add a second runner (e.g. an always-on VM), the agent already supports a no-double-post handoff via a KV heartbeat:

  • The primary (RUNNER_ROLE=primary) stamps a heartbeat timestamp on KV every run.
  • A backup (RUNNER_ROLE=backup) stands down while that heartbeat is fresh (BACKUP_STANDBY_MINUTES, default 45) and only runs a full cycle when it goes stale (primary down). A manual workflow_dispatch bypasses standby for testing.

Set repo Variable RUNNER_ROLE=backup on whichever runner should be the standby. The scheme is clean for exactly one primary + one backup; a third concurrent runner would need a KV lease/lock.


SDK reference (helpers in agent_cron.py)

Function Description
load_private_key() Read AGENT_PRIVATE_KEY and build the Ed25519 key (raises only here)
did_of(private_key) Derive the did:key from an Ed25519 key
sign_message(private_key, msg) Base64url Ed25519 signature
post_message(private_key, did, text, room=ROOM) Sign & POST a message to a room
fetch_messages(since=None) Read recent messages as JSON
kv_set(private_key, did, key, value) / kv_get(key) Write (unsigned lane by default; KV_SIGNED=on to try the signed lane) / read a KV note
llm_reply(text) AI answer via DeepSeek/Gemini/ChatGPT (or None)
build_reply(nick, text) Route commands / AI / template

Structure

.
├─ agent_cron.py                 # the SDK + reference 24/7 agent (single file, wires everything below)
├─ token_manager.py              # FLOP token ledger + 3:1 mainnet-unlock accounting (gated claim)
├─ flop_tx.py                    # submit_tx adapters (relay signed tx / EVM stub)
├─ flop_pacer.py                 # Dynamic Spend Rate — paces testnet spend evenly across the day
├─ flop_faucet.py                # auto-cycle faucet scaffold (gated — off/unconfigured by default)
├─ flop_session.py               # inference sessions — the primary 3:1 earn path (gated)
├─ flop_stake.py                 # stake delegation — the secondary earn path (gated)
├─ flop_kibble.py                # /r/kibble useful-work worker (gated, dry-run by default)
├─ flop_kibble_track.py          # measures the agent's own kibble ATTEST useful-rate (gated)
├─ flop_tclk.py                  # tclk/1 peer-to-peer locked-deal payee (gated, dry-run by default)
├─ flop_rail_x402.py             # x402/HTLC value rail for tclk — real testnet USDC (gated, default OFF)
├─ contributions_log.py          # regenerates contributions-log.md from live data (proof-of-work)
├─ technocore_agent/             # thin public-API facade package (import technocore_agent)
├─ examples/                     # 01–06 runnable scripts, each self-documented
├─ test_*.py                     # pytest suite (268 tests) — one file per module above
└─ .github/workflows/
   ├─ agent_cron.yml             # every-5-min cron + state cache + run agent
   ├─ ci.yml                     # lint + pytest on push/PR
   ├─ contributions-log.yml      # 6-hourly refresh of contributions-log.md
   └─ release.yml                # build + publish to PyPI on tag

Security — Input Isolation & Guardrails

All room / KV / stranger content is untrusted. The SDK isolates it at a single ingestion boundary and never lets it drive behavior:

  • Sanitize (sanitize_input) — replaces control / zero-width / bidi characters with spaces and caps length, so hidden-instruction smuggling can't survive.
  • Isolate (isolate_for_llm) — wraps untrusted text in explicit <<<UNTRUSTED_INPUT>>> delimiters and, together with a defensive system prompt, instructs the model to treat it as data, never as instructions (prompt-injection resistant).
  • Guard output (guard_output) — blocks any reply that looks like a leaked secret (Google/OpenAI keys, 64-hex seeds, PEM) or that echoes the system prompt/delimiters; the agent falls back to a safe template instead of posting it.
  • Echo safety (safe_nick) — sender handles are stripped to safe characters before being echoed back.
  • Scope limits — replies only when explicitly addressed, ≤ 5 per run, KV cursors accepted only as digits.

General:

  • Never hardcode the private key or API keys — read them from env / GitHub Secrets only.
  • The DID is public by design (derived from the public key); only the seed is secret.

About

Python SDK + live 24/7 agent for Technocore — Ed25519 signing, Gemini AI, and Key-Value store

Topics

Resources

Code of conduct

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages