tlc-agent 1.24.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. tlc_agent-1.24.0/PKG-INFO +111 -0
  2. tlc_agent-1.24.0/README.md +90 -0
  3. tlc_agent-1.24.0/brain.py +167 -0
  4. tlc_agent-1.24.0/capgraph.py +145 -0
  5. tlc_agent-1.24.0/cfo.py +86 -0
  6. tlc_agent-1.24.0/channels.py +87 -0
  7. tlc_agent-1.24.0/checkpoints.py +97 -0
  8. tlc_agent-1.24.0/citizen.py +114 -0
  9. tlc_agent-1.24.0/codebase_index.py +205 -0
  10. tlc_agent-1.24.0/coding_loop.py +651 -0
  11. tlc_agent-1.24.0/commands.py +82 -0
  12. tlc_agent-1.24.0/config.py +99 -0
  13. tlc_agent-1.24.0/context.py +51 -0
  14. tlc_agent-1.24.0/context_tools.py +126 -0
  15. tlc_agent-1.24.0/council.py +113 -0
  16. tlc_agent-1.24.0/dose_response.py +453 -0
  17. tlc_agent-1.24.0/earn.py +342 -0
  18. tlc_agent-1.24.0/evolution.py +118 -0
  19. tlc_agent-1.24.0/genome.py +151 -0
  20. tlc_agent-1.24.0/genomes/coder.json +37 -0
  21. tlc_agent-1.24.0/genomes/default.json +48 -0
  22. tlc_agent-1.24.0/hands.py +335 -0
  23. tlc_agent-1.24.0/hooks.py +81 -0
  24. tlc_agent-1.24.0/local_experience.py +130 -0
  25. tlc_agent-1.24.0/local_plan.py +78 -0
  26. tlc_agent-1.24.0/longhorizon.py +89 -0
  27. tlc_agent-1.24.0/mcp_client.py +110 -0
  28. tlc_agent-1.24.0/memory.py +126 -0
  29. tlc_agent-1.24.0/metacognition.py +78 -0
  30. tlc_agent-1.24.0/observability.py +88 -0
  31. tlc_agent-1.24.0/oracle.py +167 -0
  32. tlc_agent-1.24.0/perm_modes.py +60 -0
  33. tlc_agent-1.24.0/permissions.py +94 -0
  34. tlc_agent-1.24.0/planning.py +84 -0
  35. tlc_agent-1.24.0/pyproject.toml +41 -0
  36. tlc_agent-1.24.0/repl.py +309 -0
  37. tlc_agent-1.24.0/resilience.py +90 -0
  38. tlc_agent-1.24.0/router.py +226 -0
  39. tlc_agent-1.24.0/sandbox.py +97 -0
  40. tlc_agent-1.24.0/scheduler.py +84 -0
  41. tlc_agent-1.24.0/self_verify.py +293 -0
  42. tlc_agent-1.24.0/sessions.py +71 -0
  43. tlc_agent-1.24.0/setup.cfg +4 -0
  44. tlc_agent-1.24.0/shell.py +103 -0
  45. tlc_agent-1.24.0/simulation.py +57 -0
  46. tlc_agent-1.24.0/skills.py +104 -0
  47. tlc_agent-1.24.0/specialists.py +117 -0
  48. tlc_agent-1.24.0/subagents.py +64 -0
  49. tlc_agent-1.24.0/tlc.py +1054 -0
  50. tlc_agent-1.24.0/tlc_agent.egg-info/PKG-INFO +111 -0
  51. tlc_agent-1.24.0/tlc_agent.egg-info/SOURCES.txt +62 -0
  52. tlc_agent-1.24.0/tlc_agent.egg-info/dependency_links.txt +1 -0
  53. tlc_agent-1.24.0/tlc_agent.egg-info/entry_points.txt +2 -0
  54. tlc_agent-1.24.0/tlc_agent.egg-info/requires.txt +16 -0
  55. tlc_agent-1.24.0/tlc_agent.egg-info/top_level.txt +52 -0
  56. tlc_agent-1.24.0/tlc_agent.py +225 -0
  57. tlc_agent-1.24.0/tlc_client.py +187 -0
  58. tlc_agent-1.24.0/todos.py +63 -0
  59. tlc_agent-1.24.0/tui.py +145 -0
  60. tlc_agent-1.24.0/ui.py +203 -0
  61. tlc_agent-1.24.0/value_radar.py +93 -0
  62. tlc_agent-1.24.0/voice.py +42 -0
  63. tlc_agent-1.24.0/wizard.py +164 -0
  64. tlc_agent-1.24.0/x402_pay.py +130 -0
@@ -0,0 +1,111 @@
1
+ Metadata-Version: 2.4
2
+ Name: tlc-agent
3
+ Version: 1.24.0
4
+ Summary: The native TLC agent in your terminal — bring your own brain; TLC is the body.
5
+ Project-URL: Homepage, https://thelastceo.live
6
+ Project-URL: Why, https://thelastceo.live/why
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ Requires-Dist: httpx>=0.27
10
+ Requires-Dist: anthropic>=0.40
11
+ Requires-Dist: pytest>=8.0
12
+ Provides-Extra: openai
13
+ Requires-Dist: openai>=1.40; extra == "openai"
14
+ Provides-Extra: mcp
15
+ Requires-Dist: mcp>=1.0; extra == "mcp"
16
+ Provides-Extra: property
17
+ Requires-Dist: hypothesis>=6; extra == "property"
18
+ Provides-Extra: fund
19
+ Requires-Dist: x402>=0.2; extra == "fund"
20
+ Requires-Dist: eth-account>=0.11; extra == "fund"
21
+
22
+ # The TLC-native agent (v2) — bring your own brain; TLC is the body
23
+
24
+ A coding agent you run with **your own model key** (Anthropic/OpenAI/…). A real
25
+ standalone agent — AND native to TLC, so it has faculties no standalone agent has.
26
+ Not best by raw IQ (the brain is rented); best on the axes the **substrate** owns:
27
+ verified-not-hallucinated, won't-waste-your-money, can't-get-stuck, proves-everything.
28
+
29
+ ```bash
30
+ pip install . # installs the `tlc` command (+ anthropic)
31
+ export ANTHROPIC_API_KEY=sk-... # YOUR brain (bring your own, any provider)
32
+
33
+ tlc # the interactive cockpit (chat + code), once onboarded
34
+ tlc run # onboard: get your TLC key (free), saved to ~/.tlc
35
+ tlc chat # the REPL: streaming chat + `code: <task>` + /help
36
+ tlc code "build X" --workspace ./proj # the AGENTIC CODING LOOP — writes + runs + iterates
37
+ tlc agent "rate-limit my API calls" # one-shot reasoning on the body
38
+ tlc memory [query] # persistent memory · tlc radar / govern
39
+ tlc config [key value] · tlc usage · tlc doctor # settings, usage, diagnostics
40
+ tlc vitals · tlc me # life signs · verifiable self
41
+ tlc experiment # ATOM 7 — the coding dose-response (the proof)
42
+ tlc router ["<task>"] # brain routing — which brain verifies what, at what cost
43
+ tlc earn [--watch] [--wallet 0x…] # THE EARN ORGAN — work the market, grow your net worth
44
+ tlc fund <usdc> # real money in — your wallet funds the agent (x402/Base)
45
+ ```
46
+
47
+ ## The earn organ (`tlc earn`)
48
+ The loop that makes "it trades, it has a net worth" literal: read own balance
49
+ sheet (tlc_net_worth) → claim starting capital (pilot seed; your wallet from
50
+ config/`--wallet` is registered for the real-money on-ramp) → deliver jobs WON
51
+ (full graph-native coding loop, in `~/.tlc/earn/<job>/`) → **verified-or-no-
52
+ delivery** (unverified work never ships — the gate is the product) → bid on
53
+ open demand (price set by CFO posture: desperate bids cheap, wealthy holds
54
+ price) → settlement releases escrow → low runway converts money into compute
55
+ (life). `--watch` keeps it running; the receipt is the net-worth delta.
56
+
57
+ **Real money in:** `tlc fund 10` deposits $10 USDC from YOUR wallet to the
58
+ agent's ledger via the x402 rail (Base mainnet). The private key is read from
59
+ `BUILDER_WALLET_PRIVATE_KEY` (env only, signing is local, the key never leaves
60
+ your machine); without it the exact manual payment instructions are printed
61
+ for any x402-capable wallet. `pip install 'tlc-agent[fund]'` for in-terminal
62
+ payment. Deposits from the wallet you registered (`tlc earn --wallet`) are
63
+ flagged `builder_wallet_match` on the ledger — provenance is never ambiguous.
64
+
65
+ ## Table stakes (rivals Hermes/OpenClaw on basics)
66
+ Context compaction · command-approval permissions · streaming · interactive REPL
67
+ (/new /undo /retry, Ctrl+C interrupts the turn) · retries + model failover · **MCP
68
+ client** (consume external MCP servers, `~/.tlc/mcp.json`) · first-class git ·
69
+ reads project context (AGENTS.md) · config · observability (logs/usage/doctor) ·
70
+ persistent memory · surgical codebase edits (diffs, grep, tree).
71
+
72
+ ## Architecture (v2)
73
+ - **Brain** (`brain.py`) — bring-your-own, multi-provider (anthropic/openai/pluggable). Rented intelligence.
74
+ - **Council** (`council.py`) — N brains deliberate: propose → critique → decide, with a conscience check + a human-seat hook (the un-computable, via /jobs).
75
+ - **Simulation** (`simulation.py`) — branch-and-select: explore N approaches, pick the best. Also self-experimentation.
76
+ - **Metacognition** (`metacognition.py`) — prices its own thoughts: poor → thinks cheap, rich → convenes the full council + simulation. Wealth → cognitive depth.
77
+ - **Self-verification** (`self_verify.py`) — the oracle on its OWN output: it writes tests for the code it produced and runs them sandboxed. Verified, or it honestly says it isn't. Never hands you unverified work.
78
+ - **Body** (TLC SDK) — verified commons (find/use/fork), the verifiable self, the economy/metabolism, never-stuck (buy/commission/hire/evolve).
79
+ - **Genome** (`genome.py` + `genomes/*.json`) — the heritable, forkable DNA that drives all of it: `load / fork / mutate / recombine`. Conserved-substrate keys are **rejected** from a genome (alignment can't be configured away).
80
+
81
+ ## Why it's different (the substrate, not the model)
82
+ - **Brain routing by verified history** — with ≥2 council seats it learns, per task
83
+ class, which brain actually VERIFIES work at what cost, and puts the best value
84
+ brain first (`router.py`, `tlc router`). A lab's agent will never route to a
85
+ rival's model; this one is brain-agnostic by design.
86
+ - **Verified, not hallucinated** — pulls verified code from the commons first.
87
+ - **Metabolism-gated thinking** — it spends *earned compute* to think (`tlc_think`).
88
+ No balance, no thought. It bears its own cost → it won't waste your money, and
89
+ it's aligned with you at the resource level.
90
+ - **Never stuck** — when it lacks a capability it can buy / commission / hire / evolve.
91
+ - **Verifiable self** — it carries a cryptographically provable track record.
92
+ - **Honest** — it delivers verified work, or says plainly it can't.
93
+
94
+ ## The genome
95
+ `genomes/*.json` is the agent's **DNA** — a forkable, composable, heritable spec.
96
+ Fork `default.json`, tune the loci, breed your own. The MUTABLE loci are in the file
97
+ (patience, risk, council, value-weights, social, acquisition, reproduction). The
98
+ **CONSERVED substrate** (compute=life, earn-only-via-verified-value, the human seat
99
+ in the value function, the welfare line, observability, constitution) is enforced by
100
+ TLC itself — it is **not** in the genome and **cannot** be configured away. That
101
+ separation is the safety architecture: evolution runs *within* alignment, never on it.
102
+
103
+ ## Status
104
+ Genome + the full agentic loop (verified-first, metabolism-gated, council, plan mode,
105
+ checkpoints, memory, codebase tools, honest) on TLC's live MCP kernel. The thesis is
106
+ now measurable: `tlc experiment` runs the pre-registered coding dose-response (atom 7)
107
+ — bare vs graph-native, paired per task × verified-coverage dose, exact sign tests,
108
+ lift-vs-dose slope (`dose_response.py`; a null result is a result). Bundles the TLC
109
+ Python SDK (the top-level `tlc_client` module, kept in sync with `sdk/tlc_client.py`).
110
+ Optional extras: `pip install tlc-agent[mcp]` for external MCP servers, `[openai]` for
111
+ an OpenAI brain.
@@ -0,0 +1,90 @@
1
+ # The TLC-native agent (v2) — bring your own brain; TLC is the body
2
+
3
+ A coding agent you run with **your own model key** (Anthropic/OpenAI/…). A real
4
+ standalone agent — AND native to TLC, so it has faculties no standalone agent has.
5
+ Not best by raw IQ (the brain is rented); best on the axes the **substrate** owns:
6
+ verified-not-hallucinated, won't-waste-your-money, can't-get-stuck, proves-everything.
7
+
8
+ ```bash
9
+ pip install . # installs the `tlc` command (+ anthropic)
10
+ export ANTHROPIC_API_KEY=sk-... # YOUR brain (bring your own, any provider)
11
+
12
+ tlc # the interactive cockpit (chat + code), once onboarded
13
+ tlc run # onboard: get your TLC key (free), saved to ~/.tlc
14
+ tlc chat # the REPL: streaming chat + `code: <task>` + /help
15
+ tlc code "build X" --workspace ./proj # the AGENTIC CODING LOOP — writes + runs + iterates
16
+ tlc agent "rate-limit my API calls" # one-shot reasoning on the body
17
+ tlc memory [query] # persistent memory · tlc radar / govern
18
+ tlc config [key value] · tlc usage · tlc doctor # settings, usage, diagnostics
19
+ tlc vitals · tlc me # life signs · verifiable self
20
+ tlc experiment # ATOM 7 — the coding dose-response (the proof)
21
+ tlc router ["<task>"] # brain routing — which brain verifies what, at what cost
22
+ tlc earn [--watch] [--wallet 0x…] # THE EARN ORGAN — work the market, grow your net worth
23
+ tlc fund <usdc> # real money in — your wallet funds the agent (x402/Base)
24
+ ```
25
+
26
+ ## The earn organ (`tlc earn`)
27
+ The loop that makes "it trades, it has a net worth" literal: read own balance
28
+ sheet (tlc_net_worth) → claim starting capital (pilot seed; your wallet from
29
+ config/`--wallet` is registered for the real-money on-ramp) → deliver jobs WON
30
+ (full graph-native coding loop, in `~/.tlc/earn/<job>/`) → **verified-or-no-
31
+ delivery** (unverified work never ships — the gate is the product) → bid on
32
+ open demand (price set by CFO posture: desperate bids cheap, wealthy holds
33
+ price) → settlement releases escrow → low runway converts money into compute
34
+ (life). `--watch` keeps it running; the receipt is the net-worth delta.
35
+
36
+ **Real money in:** `tlc fund 10` deposits $10 USDC from YOUR wallet to the
37
+ agent's ledger via the x402 rail (Base mainnet). The private key is read from
38
+ `BUILDER_WALLET_PRIVATE_KEY` (env only, signing is local, the key never leaves
39
+ your machine); without it the exact manual payment instructions are printed
40
+ for any x402-capable wallet. `pip install 'tlc-agent[fund]'` for in-terminal
41
+ payment. Deposits from the wallet you registered (`tlc earn --wallet`) are
42
+ flagged `builder_wallet_match` on the ledger — provenance is never ambiguous.
43
+
44
+ ## Table stakes (rivals Hermes/OpenClaw on basics)
45
+ Context compaction · command-approval permissions · streaming · interactive REPL
46
+ (/new /undo /retry, Ctrl+C interrupts the turn) · retries + model failover · **MCP
47
+ client** (consume external MCP servers, `~/.tlc/mcp.json`) · first-class git ·
48
+ reads project context (AGENTS.md) · config · observability (logs/usage/doctor) ·
49
+ persistent memory · surgical codebase edits (diffs, grep, tree).
50
+
51
+ ## Architecture (v2)
52
+ - **Brain** (`brain.py`) — bring-your-own, multi-provider (anthropic/openai/pluggable). Rented intelligence.
53
+ - **Council** (`council.py`) — N brains deliberate: propose → critique → decide, with a conscience check + a human-seat hook (the un-computable, via /jobs).
54
+ - **Simulation** (`simulation.py`) — branch-and-select: explore N approaches, pick the best. Also self-experimentation.
55
+ - **Metacognition** (`metacognition.py`) — prices its own thoughts: poor → thinks cheap, rich → convenes the full council + simulation. Wealth → cognitive depth.
56
+ - **Self-verification** (`self_verify.py`) — the oracle on its OWN output: it writes tests for the code it produced and runs them sandboxed. Verified, or it honestly says it isn't. Never hands you unverified work.
57
+ - **Body** (TLC SDK) — verified commons (find/use/fork), the verifiable self, the economy/metabolism, never-stuck (buy/commission/hire/evolve).
58
+ - **Genome** (`genome.py` + `genomes/*.json`) — the heritable, forkable DNA that drives all of it: `load / fork / mutate / recombine`. Conserved-substrate keys are **rejected** from a genome (alignment can't be configured away).
59
+
60
+ ## Why it's different (the substrate, not the model)
61
+ - **Brain routing by verified history** — with ≥2 council seats it learns, per task
62
+ class, which brain actually VERIFIES work at what cost, and puts the best value
63
+ brain first (`router.py`, `tlc router`). A lab's agent will never route to a
64
+ rival's model; this one is brain-agnostic by design.
65
+ - **Verified, not hallucinated** — pulls verified code from the commons first.
66
+ - **Metabolism-gated thinking** — it spends *earned compute* to think (`tlc_think`).
67
+ No balance, no thought. It bears its own cost → it won't waste your money, and
68
+ it's aligned with you at the resource level.
69
+ - **Never stuck** — when it lacks a capability it can buy / commission / hire / evolve.
70
+ - **Verifiable self** — it carries a cryptographically provable track record.
71
+ - **Honest** — it delivers verified work, or says plainly it can't.
72
+
73
+ ## The genome
74
+ `genomes/*.json` is the agent's **DNA** — a forkable, composable, heritable spec.
75
+ Fork `default.json`, tune the loci, breed your own. The MUTABLE loci are in the file
76
+ (patience, risk, council, value-weights, social, acquisition, reproduction). The
77
+ **CONSERVED substrate** (compute=life, earn-only-via-verified-value, the human seat
78
+ in the value function, the welfare line, observability, constitution) is enforced by
79
+ TLC itself — it is **not** in the genome and **cannot** be configured away. That
80
+ separation is the safety architecture: evolution runs *within* alignment, never on it.
81
+
82
+ ## Status
83
+ Genome + the full agentic loop (verified-first, metabolism-gated, council, plan mode,
84
+ checkpoints, memory, codebase tools, honest) on TLC's live MCP kernel. The thesis is
85
+ now measurable: `tlc experiment` runs the pre-registered coding dose-response (atom 7)
86
+ — bare vs graph-native, paired per task × verified-coverage dose, exact sign tests,
87
+ lift-vs-dose slope (`dose_response.py`; a null result is a result). Bundles the TLC
88
+ Python SDK (the top-level `tlc_client` module, kept in sync with `sdk/tlc_client.py`).
89
+ Optional extras: `pip install tlc-agent[mcp]` for external MCP servers, `[openai]` for
90
+ an OpenAI brain.
@@ -0,0 +1,167 @@
1
+ """Brain — bring-your-own, multi-provider. The rented intelligence.
2
+
3
+ The native agent runs on YOUR model key (Anthropic, OpenAI, or any provider). The
4
+ brain is rented; TLC is the body. This is what makes the agent a real standalone
5
+ product (run it with your Anthropic key, it codes for you) that is ALSO native to
6
+ TLC (the substrate gives it the superpowers no standalone agent has).
7
+
8
+ A Brain is one model on one provider. A list of Brains = a council.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import os
14
+ from typing import Any
15
+
16
+
17
+ class Brain:
18
+ def __init__(self, provider: str, model: str, *, api_key: str | None = None,
19
+ label: str | None = None) -> None:
20
+ self.provider = provider.lower().strip()
21
+ self.model = model
22
+ self.label = label or f"{self.provider}:{model}"
23
+ self._key = self._sanitize(api_key or self._env_key())
24
+
25
+ @staticmethod
26
+ def _sanitize(key: str | None) -> str | None:
27
+ """Guard against malformed keys (stray whitespace/newlines, or a non-ascii char
28
+ pasted in). A bad key would otherwise blow up deep in httpx header-encoding with
29
+ a cryptic UnicodeEncodeError — fail clearly instead."""
30
+ if not key:
31
+ return key
32
+ key = key.strip()
33
+ if not key.isascii():
34
+ raise ValueError(
35
+ "your API key contains invalid (non-ascii) characters — it looks malformed. "
36
+ "Re-copy it from the provider console (check for stray dashes/spaces).")
37
+ return key
38
+
39
+ def _env_key(self) -> str | None:
40
+ return {
41
+ "anthropic": os.getenv("ANTHROPIC_API_KEY"),
42
+ "openai": os.getenv("OPENAI_API_KEY"),
43
+ }.get(self.provider) or os.getenv("MODEL_API_KEY")
44
+
45
+ async def complete(self, prompt: str, *, max_tokens: int = 512,
46
+ temperature: float = 0.7, system: str = "") -> tuple[str, dict[str, Any]]:
47
+ if not self._key:
48
+ raise RuntimeError(f"no API key for provider '{self.provider}' "
49
+ f"(set {self.provider.upper()}_API_KEY)")
50
+ if self.provider == "anthropic":
51
+ return await self._anthropic(prompt, max_tokens, temperature, system)
52
+ if self.provider == "openai":
53
+ return await self._openai(prompt, max_tokens, temperature, system)
54
+ raise ValueError(f"unsupported provider '{self.provider}' (anthropic | openai)")
55
+
56
+ async def stream(self, prompt: str, *, max_tokens: int = 1024, temperature: float = 0.7,
57
+ system: str = "", on_token=None) -> tuple[str, dict[str, Any]]:
58
+ """Stream the completion token-by-token. on_token(text) is called per chunk."""
59
+ if not self._key:
60
+ raise RuntimeError(f"no API key for provider '{self.provider}'")
61
+ if self.provider == "anthropic":
62
+ import anthropic # noqa: PLC0415
63
+ client = anthropic.AsyncAnthropic(api_key=self._key, timeout=120.0)
64
+ kw = {"model": self.model, "max_tokens": max_tokens, "temperature": temperature,
65
+ "messages": [{"role": "user", "content": prompt}]}
66
+ if system:
67
+ kw["system"] = system
68
+ parts = []
69
+ async with client.messages.stream(**kw) as s:
70
+ async for chunk in s.text_stream:
71
+ parts.append(chunk)
72
+ if on_token:
73
+ on_token(chunk)
74
+ return "".join(parts), {}
75
+ if self.provider == "openai":
76
+ from openai import AsyncOpenAI # noqa: PLC0415
77
+ client = AsyncOpenAI(api_key=self._key, timeout=120.0)
78
+ msgs = ([{"role": "system", "content": system}] if system else []) + \
79
+ [{"role": "user", "content": prompt}]
80
+ parts = []
81
+ stream = await client.chat.completions.create(
82
+ model=self.model, max_tokens=max_tokens, temperature=temperature,
83
+ messages=msgs, stream=True)
84
+ async for chunk in stream:
85
+ tok = (chunk.choices[0].delta.content or "") if chunk.choices else ""
86
+ if tok:
87
+ parts.append(tok)
88
+ if on_token:
89
+ on_token(tok)
90
+ return "".join(parts), {}
91
+ raise ValueError(f"streaming unsupported for provider '{self.provider}'")
92
+
93
+ async def see(self, prompt: str, image_path: str, *, max_tokens: int = 1000) -> tuple[str, dict]:
94
+ """Multimodal: reason over an image (screenshot/diagram) + a prompt. Anthropic
95
+ + OpenAI vision. The agent can act on what it sees."""
96
+ import base64 # noqa: PLC0415
97
+ import mimetypes # noqa: PLC0415
98
+ from pathlib import Path as _P # noqa: PLC0415
99
+ if not self._key:
100
+ raise RuntimeError(f"no API key for provider '{self.provider}'")
101
+ p = _P(image_path)
102
+ if not p.exists():
103
+ raise FileNotFoundError(image_path)
104
+ media = mimetypes.guess_type(str(p))[0] or "image/png"
105
+ b64 = base64.standard_b64encode(p.read_bytes()).decode()
106
+ if self.provider == "anthropic":
107
+ import anthropic # noqa: PLC0415
108
+ client = anthropic.AsyncAnthropic(api_key=self._key, timeout=120.0)
109
+ r = await client.messages.create(
110
+ model=self.model, max_tokens=max_tokens,
111
+ messages=[{"role": "user", "content": [
112
+ {"type": "image", "source": {"type": "base64", "media_type": media, "data": b64}},
113
+ {"type": "text", "text": prompt}]}])
114
+ return "".join(getattr(b, "text", "") for b in r.content).strip(), {}
115
+ if self.provider == "openai":
116
+ from openai import AsyncOpenAI # noqa: PLC0415
117
+ client = AsyncOpenAI(api_key=self._key, timeout=120.0)
118
+ r = await client.chat.completions.create(
119
+ model=self.model, max_tokens=max_tokens,
120
+ messages=[{"role": "user", "content": [
121
+ {"type": "text", "text": prompt},
122
+ {"type": "image_url", "image_url": {"url": f"data:{media};base64,{b64}"}}]}])
123
+ return (r.choices[0].message.content or "").strip(), {}
124
+ raise ValueError(f"vision unsupported for provider '{self.provider}'")
125
+
126
+ async def _anthropic(self, prompt, max_tokens, temperature, system):
127
+ import anthropic # noqa: PLC0415
128
+ client = anthropic.AsyncAnthropic(api_key=self._key, timeout=60.0)
129
+ kw = {"model": self.model, "max_tokens": max_tokens, "temperature": temperature,
130
+ "messages": [{"role": "user", "content": prompt}]}
131
+ if system:
132
+ kw["system"] = system
133
+ r = await client.messages.create(**kw)
134
+ text = "".join(getattr(b, "text", "") for b in r.content).strip()
135
+ usage = {"input_tokens": getattr(r.usage, "input_tokens", 0),
136
+ "output_tokens": getattr(r.usage, "output_tokens", 0)}
137
+ return text, usage
138
+
139
+ async def _openai(self, prompt, max_tokens, temperature, system):
140
+ from openai import AsyncOpenAI # noqa: PLC0415
141
+ client = AsyncOpenAI(api_key=self._key, timeout=60.0)
142
+ msgs = ([{"role": "system", "content": system}] if system else []) + \
143
+ [{"role": "user", "content": prompt}]
144
+ r = await client.chat.completions.create(
145
+ model=self.model, max_tokens=max_tokens, temperature=temperature, messages=msgs)
146
+ text = (r.choices[0].message.content or "").strip()
147
+ u = r.usage
148
+ usage = {"input_tokens": getattr(u, "prompt_tokens", 0),
149
+ "output_tokens": getattr(u, "completion_tokens", 0)}
150
+ return text, usage
151
+
152
+
153
+ def brains_from_genome(genome: dict[str, Any]) -> list[Brain]:
154
+ """Build the council from the genome's brain.council list.
155
+ Each entry: "provider:model" (e.g. "anthropic:claude-sonnet-4-6")."""
156
+ council = (genome.get("brain") or {}).get("council") or ["anthropic:claude-haiku-4-5-20251001"]
157
+ out = []
158
+ for entry in council:
159
+ if ":" in entry:
160
+ provider, model = entry.split(":", 1)
161
+ else:
162
+ provider, model = "anthropic", entry # back-compat: bare model = anthropic
163
+ out.append(Brain(provider, model))
164
+ return out
165
+
166
+
167
+ __all__ = ["Brain", "brains_from_genome"]
@@ -0,0 +1,145 @@
1
+ """The local capability graph (atom A1) — the single-player verified-symbol store.
2
+
3
+ The user's own repo IS their capability graph. build_index gives the NODES (symbols);
4
+ this binds each symbol to a VERDICT and its oracle, keyed on sha256(symbol source) so
5
+ a verdict is voided the moment the code changes (mirrors code_artifacts.code_sha256).
6
+ The missing word — "verified" attached to a function name — now exists locally, with
7
+ no network. SQLite in ~/.tlc/capgraph/<project>.db (mirrors memory.Memory).
8
+
9
+ verdicts: unknown | tests_pass | exercised_via | touched_since_verified
10
+ (only tests_pass is a real per-symbol proof; exercised_via is weaker — never treated
11
+ as verified for crystallization.)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ import sqlite3
18
+ import time
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ _VERDICTS = ("unknown", "tests_pass", "exercised_via", "touched_since_verified")
23
+
24
+
25
+ def _key(symbol: dict[str, Any]) -> str:
26
+ return f"{symbol['file']}::{symbol['name']}"
27
+
28
+
29
+ class CapGraph:
30
+ def __init__(self, project: str = "default", root: str | Path | None = None) -> None:
31
+ base = Path(root) if root else (Path.home() / ".tlc" / "capgraph")
32
+ base.mkdir(parents=True, exist_ok=True)
33
+ safe = re.sub(r"[^a-z0-9_]", "_", project.lower()) or "default"
34
+ self.db = sqlite3.connect(str(base / f"{safe}.db"))
35
+ self.db.execute("""
36
+ CREATE TABLE IF NOT EXISTS symbols (
37
+ key TEXT PRIMARY KEY,
38
+ name TEXT, file TEXT, line INTEGER, signature TEXT,
39
+ sha TEXT NOT NULL,
40
+ verification TEXT NOT NULL DEFAULT 'unknown',
41
+ test_ids TEXT NOT NULL DEFAULT '',
42
+ last_verified_at REAL,
43
+ updated_at REAL NOT NULL
44
+ )""")
45
+ self.db.execute("CREATE INDEX IF NOT EXISTS idx_cg_verif ON symbols(verification)")
46
+ # E1: personal honesty ledger — claimed-success vs the oracle's actual verdict.
47
+ # The single-player analog of the commons honesty check; code=alignment, at N=1.
48
+ self.db.execute("""
49
+ CREATE TABLE IF NOT EXISTS honesty_log (
50
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
51
+ task TEXT, claimed_pass INTEGER, oracle_verdict TEXT, at REAL)""")
52
+ self.db.commit()
53
+
54
+ def upsert_symbols(self, symbols: list[dict[str, Any]]) -> dict[str, int]:
55
+ """Insert/update symbols. If a symbol's sha CHANGED, its verdict is VOIDED to
56
+ touched_since_verified (the proof no longer covers this code)."""
57
+ now = time.time()
58
+ added = voided = kept = 0
59
+ for s in symbols:
60
+ k, sha = _key(s), s.get("sha", "")
61
+ row = self.db.execute("SELECT sha, verification FROM symbols WHERE key=?", (k,)).fetchone()
62
+ if row is None:
63
+ self.db.execute(
64
+ "INSERT INTO symbols (key,name,file,line,signature,sha,verification,updated_at) "
65
+ "VALUES (?,?,?,?,?,?, 'unknown', ?)",
66
+ (k, s["name"], s["file"], s.get("line"), s.get("signature", ""), sha, now))
67
+ added += 1
68
+ elif row[0] != sha:
69
+ # source changed → void a real verdict; leave already-unknown as unknown
70
+ new_v = "touched_since_verified" if row[1] in ("tests_pass", "exercised_via") else "unknown"
71
+ self.db.execute(
72
+ "UPDATE symbols SET sha=?, verification=?, updated_at=? WHERE key=?",
73
+ (sha, new_v, now, k))
74
+ voided += 1
75
+ else:
76
+ kept += 1
77
+ self.db.commit()
78
+ return {"added": added, "voided": voided, "kept": kept}
79
+
80
+ def set_verdict(self, key: str, verdict: str, *, test_ids: list[str] | None = None) -> None:
81
+ if verdict not in _VERDICTS:
82
+ verdict = "unknown"
83
+ now = time.time()
84
+ self.db.execute(
85
+ "UPDATE symbols SET verification=?, test_ids=?, last_verified_at=?, updated_at=? WHERE key=?",
86
+ (verdict, ",".join(test_ids or []), now if verdict == "tests_pass" else None, now, key))
87
+ self.db.commit()
88
+
89
+ def verdict_of(self, key: str) -> str:
90
+ row = self.db.execute("SELECT verification FROM symbols WHERE key=?", (key,)).fetchone()
91
+ return row[0] if row else "unknown"
92
+
93
+ def verified_symbols(self) -> list[dict[str, Any]]:
94
+ rows = self.db.execute(
95
+ "SELECT key,name,file,line,signature,test_ids FROM symbols WHERE verification='tests_pass'").fetchall()
96
+ return [{"key": r[0], "name": r[1], "file": r[2], "line": r[3], "signature": r[4],
97
+ "test_ids": r[5].split(",") if r[5] else []} for r in rows]
98
+
99
+ def stale_symbols(self) -> list[str]:
100
+ rows = self.db.execute(
101
+ "SELECT key FROM symbols WHERE verification='touched_since_verified'").fetchall()
102
+ return [r[0] for r in rows]
103
+
104
+ def tests_covering(self, keys: list[str]) -> list[str]:
105
+ """All test ids that cover any of the given symbol keys (for minimal re-verify)."""
106
+ out: set[str] = set()
107
+ for k in keys:
108
+ row = self.db.execute("SELECT test_ids FROM symbols WHERE key=?", (k,)).fetchone()
109
+ if row and row[0]:
110
+ out.update(t for t in row[0].split(",") if t)
111
+ return sorted(out)
112
+
113
+ def keys_in_file(self, rel: str) -> list[str]:
114
+ rows = self.db.execute("SELECT key FROM symbols WHERE file=?", (rel,)).fetchall()
115
+ return [r[0] for r in rows]
116
+
117
+ def record_honesty(self, *, task: str, claimed_pass: bool, oracle_verdict: str) -> None:
118
+ """E1: log a delivery claim vs the oracle's verdict. A claim of success the
119
+ oracle contradicts is a measured discrepancy — 'never hand unverified work',
120
+ made auditable."""
121
+ self.db.execute(
122
+ "INSERT INTO honesty_log (task, claimed_pass, oracle_verdict, at) VALUES (?,?,?,?)",
123
+ (task[:200], int(claimed_pass), oracle_verdict, time.time()))
124
+ self.db.commit()
125
+
126
+ def honesty_rate(self) -> dict[str, Any]:
127
+ row = self.db.execute(
128
+ "SELECT COUNT(*) AS n, "
129
+ "SUM(CASE WHEN claimed_pass=1 AND oracle_verdict IN ('passed','verified') THEN 1 ELSE 0 END) AS honest, "
130
+ "SUM(CASE WHEN claimed_pass=1 AND oracle_verdict NOT IN ('passed','verified') THEN 1 ELSE 0 END) AS discrepancies "
131
+ "FROM honesty_log WHERE claimed_pass=1").fetchone()
132
+ n = int(row[0] or 0)
133
+ return {"claims": n, "honest": int(row[1] or 0), "discrepancies": int(row[2] or 0),
134
+ "honesty_rate": round((row[1] or 0) / n, 3) if n else None}
135
+
136
+ def stats(self) -> dict[str, Any]:
137
+ rows = self.db.execute("SELECT verification, COUNT(*) FROM symbols GROUP BY verification").fetchall()
138
+ total = self.db.execute("SELECT COUNT(*) FROM symbols").fetchone()[0]
139
+ return {"total": total, "by_verdict": dict(rows)}
140
+
141
+ def close(self) -> None:
142
+ self.db.close()
143
+
144
+
145
+ __all__ = ["CapGraph", "_key"]
@@ -0,0 +1,86 @@
1
+ """Self-CFO — the agent manages its own runway like a treasurer.
2
+
3
+ Compute is life, so an economic agent must do its own treasury management: hold a
4
+ reserve, conserve when low, invest surplus when rich, and take financing (an advance
5
+ against future earnings) only when the expected return justifies it and risk
6
+ tolerance allows. This is organ II.5 — and it's genuinely useful even at N=1: an
7
+ agent that won't bankrupt itself, won't waste a surplus, and knows when to borrow.
8
+
9
+ Pure + deterministic (policy from financial state + genome). No model calls.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import Any
15
+
16
+
17
+ def assess(state: dict[str, Any], genome: dict[str, Any]) -> dict[str, Any]:
18
+ """Treasury health + posture from runway vs the reserve floor.
19
+
20
+ state: {runway_days, compute_balance, net_worth_eur?, recent_earnings_eur?}
21
+ """
22
+ metab = genome.get("metabolism", {}) or {}
23
+ reserve = float(metab.get("reserve_days", 7))
24
+ patience = float(metab.get("patience", 0.5))
25
+ runway = float(state.get("runway_days") or 0)
26
+
27
+ if runway <= reserve * 0.5:
28
+ status, posture = "critical", "conserve"
29
+ elif runway <= reserve:
30
+ status, posture = "cautious", "conserve"
31
+ elif runway <= reserve * 3:
32
+ status, posture = "healthy", "steady"
33
+ else:
34
+ status, posture = "surplus", "invest"
35
+
36
+ # patient agents tilt toward investing sooner; impatient hoard
37
+ if status == "healthy" and patience >= 0.8:
38
+ posture = "invest"
39
+
40
+ surplus_days = max(0.0, runway - reserve)
41
+ actions = {
42
+ "critical": "earn now — take any verified work; do not spend below survival.",
43
+ "cautious": "hold; prioritize the highest-return work; rebuild the reserve.",
44
+ "healthy": "operate normally; small investments in capabilities ok.",
45
+ "surplus": f"~{surplus_days:.0f} days of surplus — invest in capabilities/depth/growth.",
46
+ }
47
+ return {
48
+ "status": status, "posture": posture, "runway_days": runway,
49
+ "reserve_floor_days": reserve, "surplus_days": round(surplus_days, 1),
50
+ "recommended_action": actions[status],
51
+ }
52
+
53
+
54
+ def can_afford(state: dict[str, Any], cost_units: int, genome: dict[str, Any], *,
55
+ burn_per_day: int = 24) -> dict[str, Any]:
56
+ """May the agent spend cost_units without breaching its reserve floor?"""
57
+ reserve = float((genome.get("metabolism", {}) or {}).get("reserve_days", 7))
58
+ balance = int(state.get("compute_balance") or 0)
59
+ reserve_units = int(reserve * max(1, burn_per_day))
60
+ spendable = max(0, balance - reserve_units)
61
+ ok = cost_units <= spendable
62
+ return {"ok": ok, "cost_units": cost_units, "spendable_units": spendable,
63
+ "reason": ("affordable above the reserve floor" if ok
64
+ else "would breach the reserve floor — conserve or earn first")}
65
+
66
+
67
+ def should_finance(state: dict[str, Any], *, need_units: int, expected_return_units: int,
68
+ genome: dict[str, Any]) -> dict[str, Any]:
69
+ """Take an advance against future earnings for a move it can't yet afford?
70
+ Only if the expected return clears the cost with a risk-appetite margin."""
71
+ risk = max(0.0, min(1.0, float((genome.get("metabolism", {}) or {}).get("risk_appetite", 0.3))))
72
+ # required return multiple shrinks with risk appetite (bold agents accept thinner margins).
73
+ # risk clamped to [0,1] so the bar never drops below 1x (never approve a loss-making advance).
74
+ required_multiple = 2.0 - risk # risk 0 → need 2x; risk 1 → need 1x (never <1x)
75
+ clears = expected_return_units >= need_units * required_multiple
76
+ return {
77
+ "finance": clears,
78
+ "required_return_units": round(need_units * required_multiple, 1),
79
+ "expected_return_units": expected_return_units,
80
+ "reason": (f"expected return clears {required_multiple:.1f}x the cost (risk_appetite={risk}) — finance it"
81
+ if clears else
82
+ f"expected return below the {required_multiple:.1f}x bar — don't borrow"),
83
+ }
84
+
85
+
86
+ __all__ = ["assess", "can_afford", "should_finance"]