Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,27 +57,45 @@ How each part works, with diagrams: [the documentation](https://jadi03.github.io
embco/
├── agent/ the agent: its brain and the checks it cannot skip
│ ├── agent.py the brain: the AI model decides, pays and acts on the result
│ ├── think.py one session: wake the model, run it, remember the result
│ ├── plan.py turns the decisions into the cycle's payment plan
│ ├── models.py what the agent decides, and what it keeps for the next session
│ ├── prompt.py the briefing the model reads
│ ├── cost.py estimates what a session cost
│ ├── wiring.py builds the agent from the settings
│ ├── tools/ what the agent can read and do
│ ├── reflexes/ what wakes it, and the cycle every 15 minutes
│ ├── guardrails/ the controls, the fixed PAY / HOLD / ASK rule and the gate
│ ├── guardrails/ the controls, the fixed rule (HOLD, ASK, notes) and the gate
│ ├── memory/ the hash-chained journal
│ └── explain/ plain-language explanations for the owner
├── services/ what the agent stands on
│ ├── erp/ the ERPNext adapter
│ ├── circle/ Circle wallets and contract calls
│ ├── circle/ Circle developer-controlled wallets (EOA and smart contract accounts)
│ ├── unified_balance/ the owner's Unified Balance (Circle Gateway), through Circle's kit
│ ├── payments/ simulate on Arc, send, follow, record
│ ├── hub/ shops/ the hosted service behind the dashboard
│ ├── signing/ supplier wallet signatures
│ ├── telegram/ the Telegram bot
│ ├── settings.py settings from the environment, checked once
│ └── cli.py the embco command
├── contracts/ ShopPayables and its factory (Solidity, Arc Foundry)
├── contracts/ ShopPayables, ShopFunding and their factories (Solidity, Arc Foundry)
├── dashboard/ the owner dashboard and the supplier page (static, MetaMask)
├── connector/ prepares an ERPNext instance
├── deploy/ install.sh and the server files it sets up
├── docs/ the documentation site (MkDocs)
└── tests/ tests for the agent and the services
```

## Circle tools

| Tool | What embco does with it |
|---|---|
| Wallets: developer-controlled wallets | Creates each shop's agent wallets (a smart contract account that sends payments, and an EOA that signs Unified Balance spends) and signs every transaction, with idempotency keys |
| Wallets: Gas Station | Pays the network fees of the agent's smart contract account, so the owner never sends it gas |
| Unified Balance Kit (`@circle-fin/unified-balance-kit`) | Moves the owner's USDC from other chains into their Unified Balance and brings it to their wallet on Arc, with the fees estimated first |
| Gateway | Holds the owner's Unified Balance; the `ShopFunding` contract deposits into it for the owner |
| USDC and Arc | The currency the suppliers are paid in, and the chain where the shop contract runs |

## Getting started

### Use ours
Expand Down
7 changes: 4 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ releases money. The money moves only when the shop contract accepts the call.
| Actor | Can do | Cannot do |
|---|---|---|
| Owner (MetaMask wallet) | Create the shop contract, set limits, approve supplier wallets, pause or resume, change the agent's address | No limit applies to the owner, and every change the owner makes is public on chain |
| Agent (Circle EOA wallet) | Pay an approved supplier, within the per-payment and weekly cap, once per invoice; pause; as the owner's delegate on a chain, bring USDC from the owner's Unified Balance to the owner's own wallet on Arc | Raise a limit, approve a payee, pay an unapproved wallet, pay twice, resume after a pause, send the owner's Unified Balance anywhere but the owner's wallet on Arc |
| Agent (Circle wallet of the shop: a smart contract account whose gas Circle pays; shops made before that have one EOA) | Pay an approved supplier, within the per-payment and weekly cap, once per invoice; pause; as the owner's delegate on a chain, bring USDC from the owner's Unified Balance to the owner's own wallet on Arc | Raise a limit, approve a payee, pay an unapproved wallet, pay twice, resume after a pause, send the owner's Unified Balance anywhere but the owner's wallet on Arc |
| Supplier | Sign a challenge with its wallet to prove the address is theirs | Change a payee on its own: the owner still approves it |
| AI model | Decide what to pay and when, send the payment itself and act on what happened, and talk to the owner | Override HOLD, pay what the checks do not allow, or bypass the contract |

Expand All @@ -42,16 +42,17 @@ The contracts live in `contracts/`.
| `agent/reflexes/wake.py` | When to wake the agent: an invoice new, changed or without its decision, a payment failed or blocked (not again for a result it already saw), the day it planned to pay something, a due date close with nothing planned, an owner answer or supplier signature, a limit change, an alarm it set, the daily round. What the agent did itself is not a reason: an invoice it paid does not wake it when it changes, an alarm about it does not ring, its own payments are not a change of money, and a hold it chose that only lost reasons since still holds. No reason, no model call |
| `services/erp` | Domain models, `LedgerAdapter` protocol, `erpnext/` adapter (client, mappers, adapter). Reads, plus one write through `PaymentWriter`: a payment final on chain becomes a submitted Payment Entry (on testnet, a draft marked TESTNET, so test USDC never closes a real invoice), drafted by ERPNext from the invoice and checked against what was paid (party, amount, invoice) before it is written, with the transaction hash and payee wallet in its custom fields. The same transaction is never written twice |
| `agent/guardrails/controls` | Context builder and one small module per control: three-way match, payee wallet, duplicates, price anomaly, payment limit, supplier status. Each returns PASS, HOLD (an unproven wallet, a duplicate bill), ASK (a first payment to a new wallet) or NOTE (evidence for the agent to weigh: receipt and quantities, price, limit, disabled supplier). A wallet never paid before is held until the supplier signs a challenge with it (proof the address is right and theirs), then the owner is asked to approve, since only a person knows whether the supplier really asked for the change. The question says who set the wallet in the ERP and when, from the supplier's change history |
| `services/hub`, `services/shops` | The hosted service behind the dashboard, a process of its own (`embco hub`). A shop's owner signs in with their wallet; the hub creates the shop's two Circle wallets before its contract, so the contract is created with its agent: a smart contract account that is the agent and sends every transaction, with its gas paid by Circle Gas Station, and an EOA in the same wallet set that only signs the owner's Unified Balance spends as their delegate (a smart contract account cannot). A shop made before this has one EOA that does both and pays its own gas. It stores each shop's ERPNext connection encrypted, tests it, and starts, stops and restarts the shop's agent through systemd |
| `services/signing` | EIP-712 wallet ownership message bound to embco and Arc testnet, and signer recovery (ordinary wallets; smart contract wallets need EIP-1271 on chain) |
| `agent/guardrails/rules` | Fixed combining rule (HOLD, then ASK, then PAY; a NOTE changes nothing and goes to the agent as something to weigh), policy config, the weekly-budget planner and owner answers: an approved ASK becomes PAY, a rejected one HOLD, only while the decision is unchanged. The owner answers with the CLI, on the invoice in ERPNext (an owner-only field, accepted only from `EMBCO_OWNER_USERS` per the ERP's change history, once per question), or, for a new wallet, by approving it in the contract. HOLD is never overridden |
| `agent/memory` | Memory between runs, stored as changes: every run, each new or changed decision, the policy when it changes, invoices that leave the unpaid list, owner answers, wallet challenges and the signatures that answer them, AI explanations, and each payment (blocked, submitted, complete, failed, or recorded in the ERP). One append-only, hash-chained sequence; it grows with events, not with runs. `DecisionJournal` protocol, SQLite store |
| `agent/reflexes` | One cycle (verify memory, decide, apply owner answers, remember, issue wallet challenges, explain, follow payments already sent and write the final ones in the ERP, then the agent's session), the interval loop that survives an ERP outage and stops on a tampered memory, and the text report |
| `services/settings`, `services/cli` | Settings from the environment or `.env`, checked once, secrets never shown; the `embco` command: `run`, `watch`, `answer`, `challenge`, `sign-wallet`, `history`, `verify`, `create-wallet`, `shop`, `hub`, `telegram` |
| `agent/explain` | A plain-language summary and next step for each HOLD or ASK decision, through the model with structured output (today Claude Haiku 5.5, at low effort; with Claude Opus and Sonnet 5.x it also gets the server-side refusal fallback). Explained once per decision and stored; a failure is logged and retried next cycle; never changes a decision. Off unless `EMBCO_EXPLAIN=on` |
| `services/circle` | Circle developer-controlled wallets over REST: a fresh entity secret ciphertext (RSA-OAEP, SHA-256) on every write, idempotency keys, wallet creation, contract execution and transaction state. Reads are retried when the connection drops; errors never carry credentials |
| `services/circle` | Circle developer-controlled wallets over REST: a fresh entity secret ciphertext (RSA-OAEP, SHA-256) on every write, idempotency keys, wallet creation (EOA or smart contract account), contract execution and transaction state. Reads are retried when the connection drops; errors never carry credentials |
| `services/payments` | Pays one invoice when the agent calls pay now, and gives back what happened, refusals and Circle errors word for word: re-reads the invoice and supplier from the ERP (same amount, USD, valid wallet), skips what the contract already marks paid or what does not fit under its weekly cap, lists for the dashboard any wallet the owner has not approved in the contract yet, including invoices asked about only because their wallet is new (approving the wallet there then counts as the owner's answer, saved in the journal like any other; a wallet approved before the question answers nothing) (`EMBCO_APPROVALS_FILE`: wallet, invoice and amount only, no supplier names), simulates the exact `Memo.memo` -> `ShopPayables.pay` call through the Arc node and names the contract's error if it would fail, sends the same bytes through Circle with an idempotency key per invoice and attempt, follows it to a final state, records every step, then writes it into the ERP. At most 3 failed transactions per invoice. Amounts become exact 6-decimal integers or are refused |
| `services/unified_balance` | The owner's Unified Balance (Circle Gateway) through Circle's Unified Balance Kit 1.8.0 and its Circle Wallets adapter, in a small Node script (`kit/`, pnpm, Node 22) the agent runs once per call, with Circle's credentials only in its environment. At start it gives the agent's Circle wallet the same address on each source chain (`deriveWalletByAddress`); then it reads the owner's balance per chain and whether the agent is a delegate there, estimates fees, and spends with the split the model chose, always minted to the contract owner's wallet on Arc through Circle's Forwarding Service. The dashboard gets the same per-chain view after each check |
| `dashboard/` | Static owner page (ethers 6.17.0 vendored, no build). Connects MetaMask, adds Arc Testnet, creates the shop from the factory, and lets the owner set the allowance and limits, approve suppliers (with one click for the wallets the agent is waiting to pay, from the agent's file), deposit USDC on another chain into their Unified Balance and authorize the agent there (Circle's Unified Balance Kit 1.8.0 with its viem adapter, bundled once into `vendor/`), pause or resume the agent, and read suppliers and payments from the contract's events. Money is parsed and shown as exact 6-decimal integers. Chain reads go through the site's own `/rpc` proxy, which adds the node credential on the server, so the page never holds one. Served with a strict Content Security Policy |
| `dashboard/` | Static owner page (ethers 6.17.0 vendored, no build). Connects MetaMask, adds Arc Testnet, creates the shop from the factory, and lets the owner set the allowance and limits, approve suppliers (with one click for the wallets the agent is waiting to pay, from the agent's file), choose the chains the agent can pay from (each one turned on creates the owner's funding contract there with the shop's limits, approves it and authorizes the agent in Gateway, in one batched confirmation when the wallet supports EIP-5792; Circle's Unified Balance Kit 1.8.0 with its viem adapter, bundled once into `vendor/`), pause or resume the agent, and read suppliers and payments from the contract's events. Money is parsed and shown as exact 6-decimal integers. Chain reads go through the site's own `/rpc` proxy, which adds the node credential on the server, so the page never holds one. The shop console has three columns: invoices, a short chat with the agent in the middle (a message is at most 300 characters; the details are in the activity) and the activity beside it, where the agent's reasoning is open (money that moved shows once, in the chat, as a receipt). Served with a strict Content Security Policy |
| `contracts/` (Solidity) | `ShopFunding`, one per owner on each source chain (Base Sepolia, Ethereum Sepolia), created from `ShopFundingFactory`: the agent may only move the owner's USDC there into the owner's own Gateway balance, within a per-move and a weekly cap. `ShopPayables`, one per shop, created by its owner from `ShopPayablesFactory`. The money stays in the owner's wallet; the agent can only pay approved suppliers, once per invoice, within a per-payment and a weekly cap; owner or agent can pause, only the owner resumes or changes limits. Unit and fuzz tests, plus a fork test against Arc Testnet's real USDC |

## ERP independence
Expand Down
Loading
Loading