co-agent-coder 0.1.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.
- co_agent_coder-0.1.0/CHANGELOG.md +427 -0
- co_agent_coder-0.1.0/DESIGN.md +674 -0
- co_agent_coder-0.1.0/LICENSE +201 -0
- co_agent_coder-0.1.0/MANIFEST.in +11 -0
- co_agent_coder-0.1.0/PKG-INFO +396 -0
- co_agent_coder-0.1.0/PRD.md +652 -0
- co_agent_coder-0.1.0/README.md +339 -0
- co_agent_coder-0.1.0/docs/DEVELOPMENT.md +63 -0
- co_agent_coder-0.1.0/notebooks/bootstrap.ipynb +175 -0
- co_agent_coder-0.1.0/notebooks/t4_smoke.ipynb +261 -0
- co_agent_coder-0.1.0/pyproject.toml +123 -0
- co_agent_coder-0.1.0/setup.cfg +4 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/PKG-INFO +396 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/SOURCES.txt +84 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/dependency_links.txt +1 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/entry_points.txt +5 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/requires.txt +39 -0
- co_agent_coder-0.1.0/src/co_agent_coder.egg-info/top_level.txt +1 -0
- co_agent_coder-0.1.0/src/coagent/__init__.py +84 -0
- co_agent_coder-0.1.0/src/coagent/__main__.py +6 -0
- co_agent_coder-0.1.0/src/coagent/agent/__init__.py +27 -0
- co_agent_coder-0.1.0/src/coagent/agent/budget.py +220 -0
- co_agent_coder-0.1.0/src/coagent/agent/loop.py +1102 -0
- co_agent_coder-0.1.0/src/coagent/agent/prompts.py +228 -0
- co_agent_coder-0.1.0/src/coagent/agent/state.py +181 -0
- co_agent_coder-0.1.0/src/coagent/cli.py +351 -0
- co_agent_coder-0.1.0/src/coagent/config.py +307 -0
- co_agent_coder-0.1.0/src/coagent/context/__init__.py +26 -0
- co_agent_coder-0.1.0/src/coagent/context/artifacts.py +431 -0
- co_agent_coder-0.1.0/src/coagent/context/repo_map.py +590 -0
- co_agent_coder-0.1.0/src/coagent/context/retrieval.py +375 -0
- co_agent_coder-0.1.0/src/coagent/context/tokens.py +93 -0
- co_agent_coder-0.1.0/src/coagent/evals/__init__.py +0 -0
- co_agent_coder-0.1.0/src/coagent/gitx/__init__.py +52 -0
- co_agent_coder-0.1.0/src/coagent/gitx/commit.py +91 -0
- co_agent_coder-0.1.0/src/coagent/gitx/durable.py +147 -0
- co_agent_coder-0.1.0/src/coagent/gitx/git.py +351 -0
- co_agent_coder-0.1.0/src/coagent/gitx/lock.py +157 -0
- co_agent_coder-0.1.0/src/coagent/gitx/log.py +85 -0
- co_agent_coder-0.1.0/src/coagent/gitx/resume.py +199 -0
- co_agent_coder-0.1.0/src/coagent/gitx/session.py +233 -0
- co_agent_coder-0.1.0/src/coagent/llm/__init__.py +83 -0
- co_agent_coder-0.1.0/src/coagent/llm/base.py +235 -0
- co_agent_coder-0.1.0/src/coagent/llm/grammar.py +306 -0
- co_agent_coder-0.1.0/src/coagent/llm/registry.py +147 -0
- co_agent_coder-0.1.0/src/coagent/llm/transformers_backend.py +326 -0
- co_agent_coder-0.1.0/src/coagent/py.typed +0 -0
- co_agent_coder-0.1.0/src/coagent/safety/__init__.py +24 -0
- co_agent_coder-0.1.0/src/coagent/safety/denylist.py +146 -0
- co_agent_coder-0.1.0/src/coagent/safety/policy.py +219 -0
- co_agent_coder-0.1.0/src/coagent/safety/redact.py +146 -0
- co_agent_coder-0.1.0/src/coagent/templates/config.toml +104 -0
- co_agent_coder-0.1.0/src/coagent/tools/__init__.py +155 -0
- co_agent_coder-0.1.0/src/coagent/tools/artifacts.py +307 -0
- co_agent_coder-0.1.0/src/coagent/tools/base.py +226 -0
- co_agent_coder-0.1.0/src/coagent/tools/fs.py +507 -0
- co_agent_coder-0.1.0/src/coagent/tools/notebooks.py +875 -0
- co_agent_coder-0.1.0/src/coagent/tools/pyt.py +240 -0
- co_agent_coder-0.1.0/src/coagent/tools/shell.py +558 -0
- co_agent_coder-0.1.0/src/coagent/ui/__init__.py +50 -0
- co_agent_coder-0.1.0/src/coagent/ui/gradio_app.py +353 -0
- co_agent_coder-0.1.0/src/coagent/ui/notebook.py +342 -0
- co_agent_coder-0.1.0/src/coagent/ui/render.py +422 -0
- co_agent_coder-0.1.0/tests/__init__.py +0 -0
- co_agent_coder-0.1.0/tests/_fake_gradio.py +74 -0
- co_agent_coder-0.1.0/tests/test_agent_budget.py +220 -0
- co_agent_coder-0.1.0/tests/test_agent_loop.py +342 -0
- co_agent_coder-0.1.0/tests/test_artifacts.py +423 -0
- co_agent_coder-0.1.0/tests/test_bootstrap.py +227 -0
- co_agent_coder-0.1.0/tests/test_cli.py +67 -0
- co_agent_coder-0.1.0/tests/test_config.py +76 -0
- co_agent_coder-0.1.0/tests/test_gitx.py +440 -0
- co_agent_coder-0.1.0/tests/test_llm_backend.py +175 -0
- co_agent_coder-0.1.0/tests/test_llm_grammar.py +211 -0
- co_agent_coder-0.1.0/tests/test_llm_integration.py +131 -0
- co_agent_coder-0.1.0/tests/test_llm_transformers.py +181 -0
- co_agent_coder-0.1.0/tests/test_policy.py +138 -0
- co_agent_coder-0.1.0/tests/test_repo_map.py +341 -0
- co_agent_coder-0.1.0/tests/test_retrieval.py +282 -0
- co_agent_coder-0.1.0/tests/test_safety.py +121 -0
- co_agent_coder-0.1.0/tests/test_tools_fs.py +256 -0
- co_agent_coder-0.1.0/tests/test_tools_notebooks.py +472 -0
- co_agent_coder-0.1.0/tests/test_tools_pyt.py +194 -0
- co_agent_coder-0.1.0/tests/test_tools_shell.py +211 -0
- co_agent_coder-0.1.0/tests/test_ui.py +368 -0
- co_agent_coder-0.1.0/tests/test_ui_gradio.py +260 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to coAgent. Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
4
|
+
versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## [0.1.0] - 2026-11-13
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- `src/` package layout with the module tree from `DESIGN.md` §11.
|
|
11
|
+
- `.coagent/config.toml` template shipped as package data, covering model,
|
|
12
|
+
verify, git, durability, agent budgets, tools, artifacts, safety, and UI.
|
|
13
|
+
- `coagent.config` — TOML loading, per-section dataclasses, unknown-key
|
|
14
|
+
rejection, hard 12,288-token context cap, and schema-version refusal
|
|
15
|
+
(PRD FR-DIS-5).
|
|
16
|
+
- `coagent` CLI with `init`, `doctor`, `config`, `tools`, `resume`, and
|
|
17
|
+
`models` verbs.
|
|
18
|
+
- `coagent doctor` reports the GPU envelope and whether it is a free-tier T4.
|
|
19
|
+
- Dev extras (`[llm]`, `[ui]`, `[context]`, `[colab]`, `[dev]`) so the package
|
|
20
|
+
installs and tests on CPU without torch.
|
|
21
|
+
|
|
22
|
+
### Safety layer (`coagent.safety`)
|
|
23
|
+
|
|
24
|
+
- `redact` — GitHub, Hugging Face, OpenAI, Anthropic, AWS, Google, Stripe,
|
|
25
|
+
Slack, JWT, URL-credential, `Bearer`, assigned-secret, and private-key
|
|
26
|
+
redaction. Keeps the last four characters; scrubs nested dicts and lists.
|
|
27
|
+
(PRD FR-PRV-2, FR-BOOT-5)
|
|
28
|
+
- `denylist` — tokenized destructive-command rules: force-push, push to `main`,
|
|
29
|
+
`reset --hard`, `branch -D`, `rm -rf /`, `curl | sh`, fork bombs,
|
|
30
|
+
shutdown/reboot, mkfs. Deny rules evaluate before allow rules (FR-GIT-9).
|
|
31
|
+
- `policy` — the approval matrix enforced in code. Reads are jailed (including
|
|
32
|
+
`.git`); writes are allowed only inside the repo; shell/install/network/
|
|
33
|
+
delete ask in interactive mode and **deny** in non-interactive mode
|
|
34
|
+
(FR-TOL-4). `check_push` allows only configured remotes and `coagent/*`
|
|
35
|
+
refs (FR-GIT-8).
|
|
36
|
+
|
|
37
|
+
### Tool layer (`coagent.tools`)
|
|
38
|
+
|
|
39
|
+
- Registry with JSON schemas, unknown-tool and unknown-argument rejection, and
|
|
40
|
+
type coercion, all with corrective messages (FR-TOL-1).
|
|
41
|
+
- Per-phase tool sets: SURVEY is read-only, PLAN gets **no tools at all**,
|
|
42
|
+
VERIFY cannot write (DESIGN §7.1).
|
|
43
|
+
- `read_file`, `list_dir`, `grep` — jailed reads, line-numbered output, and
|
|
44
|
+
head/tail truncation (FR-CTX-3).
|
|
45
|
+
- `edit_file` — SEARCH/REPLACE with a unique-match requirement, a fuzzy
|
|
46
|
+
nearest-region hint on failure, read-before-write enforcement (FR-AGT-4), and
|
|
47
|
+
a syntax gate that rejects an unparseable result (FR-VER-4).
|
|
48
|
+
- `write_file` — new files, or full rewrites only under 200 lines.
|
|
49
|
+
- `run_shell`, `run_python` — process-group isolation with timeout kill, a
|
|
50
|
+
scrubbed environment (no tokens reach the child), `CUDA_VISIBLE_DEVICES=""`
|
|
51
|
+
by default (PRD D4), and a 64 KB head/tail output cap (FR-TOL-3).
|
|
52
|
+
- `run_tests` — pytest output as a digest rather than a dump: pass/fail verdict,
|
|
53
|
+
failing node ids, and the `>` pointer plus `E` block for each failure, capped
|
|
54
|
+
at `verify.feedback_max_tokens` (DESIGN §7.5). Accepts `argv` (preferred, so a
|
|
55
|
+
tokenized command from config is never re-split) or a `command` string, which
|
|
56
|
+
is shlex-split without a shell. Classified EXEC, so it is unreachable from the
|
|
57
|
+
read-only SURVEY phase.
|
|
58
|
+
- `coagent.tools.pyt` — the parser behind `run_tests`: `parse_pytest_output`
|
|
59
|
+
(failed names, per-failure traces, summary, version, duration) and
|
|
60
|
+
`summarise_pytest`. Whole traces only, most recent first, with an explicit
|
|
61
|
+
omission note instead of a half-rendered traceback.
|
|
62
|
+
|
|
63
|
+
### Git layer (`coagent.gitx`) — git as the agent's database
|
|
64
|
+
|
|
65
|
+
- `git.py` — a thin, jailed git runner: scrubbed env, `GIT_TERMINAL_PROMPT=0`
|
|
66
|
+
(a missing credential errors instead of hanging), porcelain/-z output, and the
|
|
67
|
+
push allowlist enforced *before* spawn. `GIT_ASKPASS` carries the token; it is
|
|
68
|
+
never written to `.git/config` (DESIGN §9.2).
|
|
69
|
+
- `commit.py` — the six `Coagent-*` trailers from PRD Appendix B, values
|
|
70
|
+
flattened to one line so a newline in a task cannot break `--grep`. Refuses
|
|
71
|
+
an empty commit, which would corrupt step numbering.
|
|
72
|
+
- `session.py` — session branch `coagent/<date>-<id>`; **refuses a dirty tree**
|
|
73
|
+
with the list of paths and a remedy (FR-GIT-1, AT-3); refuses to work on
|
|
74
|
+
`main`; reuses an existing branch as a *resume*. coAgent's own runtime state
|
|
75
|
+
(`.coagent/lock.json`, `tmp/`, `sessions/`) is excluded from the dirty check.
|
|
76
|
+
- `lock.py` — single-writer lock with heartbeat TTL, stale expiry, and `--force`
|
|
77
|
+
takeover that reports the blocking session (FR-GIT-6).
|
|
78
|
+
- `durable.py` — `checkpoint()`: push the session branch, or fall back to a
|
|
79
|
+
`git bundle` (full first, then `A..B` increments) when the push fails or a
|
|
80
|
+
token is missing. A failed sync warns and continues (FR-GIT-4).
|
|
81
|
+
- `resume.py` — a template-extracted briefing (no LLM call) from branch
|
|
82
|
+
trailers, the plan file, and the JSONL log; falls back to trailers when a
|
|
83
|
+
hard kill left no log. `next_step` is monotonic across plan commits.
|
|
84
|
+
- `log.py` — JSONL session log, **redacted before the write** (FR-PRV-3).
|
|
85
|
+
- `coagent init` now also adds the `.coagent` runtime entries to `.gitignore`;
|
|
86
|
+
the session path only warns rather than editing a tracked file.
|
|
87
|
+
|
|
88
|
+
### Fixed
|
|
89
|
+
|
|
90
|
+
- Pinning `core.autocrlf` in the git child env made every CRLF worktree look
|
|
91
|
+
permanently modified. The value is now inherited, never overridden.
|
|
92
|
+
- `os.getpgid`/`start_new_session` do not exist on Windows; both are guarded so
|
|
93
|
+
the tool suite runs on a dev laptop while Linux keeps real process-group kills.
|
|
94
|
+
|
|
95
|
+
### Agent loop (`coagent.agent`)
|
|
96
|
+
|
|
97
|
+
- `prompts.py` — the five phase prompts in one auditable file. Tool results are
|
|
98
|
+
wrapped in `<observation>` and the system prompt declares them untrusted data,
|
|
99
|
+
which is the first layer of the prompt-injection defence (DESIGN §9.3).
|
|
100
|
+
PLAN's prompt states it has no tools.
|
|
101
|
+
- `budget.py` — hard ceilings on steps, wall clock, tokens, GPU minutes,
|
|
102
|
+
commits, and consecutive failures. Exhaustion raises `BudgetExhausted` so the
|
|
103
|
+
run checkpoints and reports instead of burning the session (FR-AGT-5).
|
|
104
|
+
`LoopDetector` fingerprints `(tool, args)`; 3 repeats force a replan, 5 stop
|
|
105
|
+
the run (DESIGN §7.4).
|
|
106
|
+
- `state.py` — `PlanFile`, the durable plan in `.coagent/plans/`, written before
|
|
107
|
+
the first edit and re-read after eviction, so memory survives both context
|
|
108
|
+
loss and VM death (FR-CTX-5). Observations are a bounded window.
|
|
109
|
+
- `loop.py` — the explicit state machine SURVEY → PLAN → ACT → VERIFY →
|
|
110
|
+
CHECKPOINT → REPORT. PLAN structurally cannot act; one write or exec per
|
|
111
|
+
turn; read-before-write; verify-then-commit; `done` is a predicate over
|
|
112
|
+
passing checks and plan completion, never the model's opinion (FR-AGT-8).
|
|
113
|
+
- `coagent run "<task>"` with `--auto` and `--dry-run` (scripted model, no GPU).
|
|
114
|
+
- `python -m coagent` for use inside a Colab cell.
|
|
115
|
+
|
|
116
|
+
### Fixed
|
|
117
|
+
|
|
118
|
+
- `git commit` failed with a bare exit 1 and empty stderr because nothing was
|
|
119
|
+
staged. The loop now stages changed and new files, never `.coagent/` state.
|
|
120
|
+
- Plan steps were never ticked, so no task could ever satisfy the definition of
|
|
121
|
+
done. A successful edit ticks the step naming that file, and passing checks
|
|
122
|
+
tick the step naming them.
|
|
123
|
+
- `Budget.check()` used `>=`, so the Nth step raised instead of the (N+1)th.
|
|
124
|
+
Ceilings are now inclusive.
|
|
125
|
+
- A final checkpoint committed failing work labelled as verified. The trailer
|
|
126
|
+
now records `failed (work in progress)`.
|
|
127
|
+
- The report phase could echo a raw JSON tool envelope at the user. It is now
|
|
128
|
+
detected with the real parser and replaced with a generated summary.
|
|
129
|
+
- A failed commit is no longer silent: the reason is emitted as a `commit_failed`
|
|
130
|
+
event.
|
|
131
|
+
|
|
132
|
+
### Model layer (`coagent.llm`)
|
|
133
|
+
|
|
134
|
+
- `base.py` — the `LLMBackend` interface (`generate`, `count_tokens`, streaming,
|
|
135
|
+
cancel, `load`/`unload`), plus `TokenBudget`, which rejects an over-cap request
|
|
136
|
+
**before** generation using the model's own tokenizer (PRD D2, FR-CTX-6), and
|
|
137
|
+
`FakeBackend`, a scripted backend that makes the whole loop testable on CPU.
|
|
138
|
+
- `grammar.py` — tolerant envelope extraction with a 5-step escalation ladder:
|
|
139
|
+
labelled fence → bare fence → bare `{...}` (balanced-brace scan that respects
|
|
140
|
+
strings) → JSON5-ish repair (single quotes, unquoted keys, trailing commas,
|
|
141
|
+
literal control characters inside strings) → failure with a **corrective
|
|
142
|
+
message** the loop feeds straight back. Never raises `JSONDecodeError` at the
|
|
143
|
+
caller. Failures are diagnosed individually: "cut off" (unbalanced braces,
|
|
144
|
+
with advice to send a smaller edit), "malformed" (bad escapes, missing comma),
|
|
145
|
+
and "no JSON object" (plain prose) are different problems and get different
|
|
146
|
+
advice.
|
|
147
|
+
- `registry.py` — exactly two validated configurations (7B/NF4/12k and
|
|
148
|
+
3B/NF4/8k fallback), and `preflight_gpu()`, which checks T4 / compute 7.5 /
|
|
149
|
+
≥14 GB VRAM and returns an actionable refusal instead of raising (PRD D1).
|
|
150
|
+
- `transformers_backend.py` — NF4 4-bit with fp16 compute and
|
|
151
|
+
`attn_implementation="sdpa"` (Turing has no bf16 and no FlashAttention-2),
|
|
152
|
+
`expandable_segments` set before torch import, `TextIteratorStreamer` on a
|
|
153
|
+
worker thread, a real `StoppingCriteria` for cancel, and the OOM ladder
|
|
154
|
+
(`shrink_context` → `halve_output` → `fallback_model` → `stop`) that unloads
|
|
155
|
+
the model and never loses the session. Torch and transformers are resolved
|
|
156
|
+
through module-level accessors, so `import coagent` stays torch-free and the
|
|
157
|
+
orchestration is testable without a GPU.
|
|
158
|
+
|
|
159
|
+
### Fixed
|
|
160
|
+
|
|
161
|
+
- `_is_cuda_oom` missed a wrapped `OutOfMemoryError` that only appeared in the
|
|
162
|
+
message text; the OOM ladder now recognises it.
|
|
163
|
+
- `OutOfMemoryLadder.exhausted` reported False after all three recovery actions;
|
|
164
|
+
it now flips once the ladder is spent.
|
|
165
|
+
|
|
166
|
+
- `.coagent/plans/` was missing from the runtime paths, so a second run refused
|
|
167
|
+
to start on coAgent's own plan files.
|
|
168
|
+
|
|
169
|
+
### Context layer (`coagent.context`)
|
|
170
|
+
|
|
171
|
+
- `repo_map` — signature-only repository map built for a 12k window
|
|
172
|
+
(DESIGN §5.1–5.2). Parses every source file with `ast` and falls back to
|
|
173
|
+
tree-sitter, then to line-anchored regex, so a file that will not parse
|
|
174
|
+
degrades the map instead of ending the session. Emits signatures and
|
|
175
|
+
module constants only — never bodies.
|
|
176
|
+
- PageRank-lite ranking, `0.5·reference_count + 0.3·recently_edited +
|
|
177
|
+
0.2·mentioned_in_task`: a shared module outranks a leaf script, and a file
|
|
178
|
+
the task names always survives the budget. Reference counting is inbound and
|
|
179
|
+
token-set based — the naive per-symbol regex version cost 4 s on a 7k-line
|
|
180
|
+
repo, now ~0.12 s.
|
|
181
|
+
- Greedy, deterministic packing into the repo map's 2000-token slot, cutting on
|
|
182
|
+
file boundaries rather than mid-signature. Vendored, generated, and
|
|
183
|
+
`__pycache__` trees are excluded; per-file signatures are capped at 40.
|
|
184
|
+
- `tokens` — ~4 chars/token estimator, `fit` for budget packing, and
|
|
185
|
+
`truncate_middle` for head/tail truncation (monotone, so an "it fits" answer
|
|
186
|
+
stays true for the real tokenizer).
|
|
187
|
+
- `context.repo_map_tokens` and `context.max_files` config knobs, validated with
|
|
188
|
+
a 200-token floor.
|
|
189
|
+
- The agent loop's `_repo_map` stand-in is replaced: SURVEY now receives the
|
|
190
|
+
ranked map, seeded with the task text and the files the session has touched.
|
|
191
|
+
|
|
192
|
+
### Retrieval (`coagent.context.retrieval`)
|
|
193
|
+
|
|
194
|
+
- `Retriever` — Okapi BM25 over function-sized chunks (DESIGN §5.3, FR-CTX-2).
|
|
195
|
+
**No vector store**, per the design: on 2 vCPU beside a GPU an embedding index
|
|
196
|
+
is a memory and latency tax for a ranking gain BM25 mostly covers at this repo
|
|
197
|
+
size. `all-MiniLM-L6-v2` stays an optional future upgrade, not a dependency.
|
|
198
|
+
- Chunks come free from the repo map: `Signature` now carries 1-based, inclusive
|
|
199
|
+
`start_line`/`end_line` from the same `ast`/tree-sitter/regex pass, so
|
|
200
|
+
retrieval does not re-parse the repo. The AST, tree-sitter, and regex parsers
|
|
201
|
+
all populate spans; the regex fallback closes a `def` on the first line that
|
|
202
|
+
dedents past its body.
|
|
203
|
+
- A module prologue chunk (imports and module docstring) is added per file,
|
|
204
|
+
because that is where a file says what it depends on. `const` signatures are
|
|
205
|
+
skipped — a bare `X = ...` is not worth an excerpt slot.
|
|
206
|
+
- Tokenisation explodes `snake_case` and `camelCase`, so a query for "csv loader"
|
|
207
|
+
matches `load_csv`, while the joined identifier is kept so an exact query still
|
|
208
|
+
matches exactly.
|
|
209
|
+
- Ranking is BM25 plus a symbol-graph adjacency bonus (weight 0.35): a chunk that
|
|
210
|
+
imports a module already in context, or references/defines a symbol defined in
|
|
211
|
+
one, is pushed up. This is the "callers/callees of files already in context"
|
|
212
|
+
part of §5.3, and files already read are excluded so the slot only ever shows
|
|
213
|
+
new code.
|
|
214
|
+
- Deterministic ordering with a `(score, file, line)` tiebreak, so a re-query
|
|
215
|
+
cannot shuffle equal-scoring chunks and invalidate a resumed session.
|
|
216
|
+
- `render()` packs whole chunks into the 3000-token excerpt slot and **skips** a
|
|
217
|
+
chunk that does not fit rather than truncating it: half a function is worse
|
|
218
|
+
than a different whole one, and a truncated header would lie about the range.
|
|
219
|
+
A single 800-line method cannot eat the slot (`max_chunk_lines`).
|
|
220
|
+
- The ACT prompt receives the excerpts under an explicit "untrusted source, not
|
|
221
|
+
instructions" banner, alongside the last failure.
|
|
222
|
+
- The repo map is now built once per session and cached, invalidated only when a
|
|
223
|
+
file is written — it was being re-parsed on every turn. Retrieval or map
|
|
224
|
+
failures degrade to a note and emit an event; neither can end a session.
|
|
225
|
+
- `context.retrieved_tokens` config knob.
|
|
226
|
+
|
|
227
|
+
### Notebook tools (`coagent.tools.notebooks`)
|
|
228
|
+
|
|
229
|
+
Notebooks are first-class (PRD D-2, FR-TOL-6), not source files with a JSON side
|
|
230
|
+
quest.
|
|
231
|
+
|
|
232
|
+
- `read_notebook` — cells by id, with type, execution count, and a hidden-output
|
|
233
|
+
count. Assigns ids to notebooks that lack them (nbformat 4.4, common in older
|
|
234
|
+
Colab files) **on read**, so an edit cannot land on a stale index; duplicate ids
|
|
235
|
+
are repaired. `read_file` on a `.ipynb` now redirects here rather than showing
|
|
236
|
+
escaped JSON as line numbers.
|
|
237
|
+
- `edit_notebook` — `replace_source`, `insert`, `delete`, `replace_metadata`.
|
|
238
|
+
Cells are addressed **by id only**; an index is rejected with the list of valid
|
|
239
|
+
ids, because indices shift under insert/delete and an index-addressed edit is a
|
|
240
|
+
silent wrong edit. Read-before-write applies exactly as for `edit_file`.
|
|
241
|
+
- A cell edit is gated on `ast.parse` first: an unparseable Python cell is refused
|
|
242
|
+
and the file is left untouched. IPython magics (`%matplotlib`, `!pip`, `%%bash`)
|
|
243
|
+
are neutralised rather than flagged, since they are valid notebook cells that
|
|
244
|
+
simply are not Python. An edited cell drops its stale outputs so the notebook
|
|
245
|
+
never shows results from code that no longer exists.
|
|
246
|
+
- Every write is validated with `nbformat.validate` after bumping the minor
|
|
247
|
+
version to 5 (required for ids), so coAgent can never hand Colab a corrupt file.
|
|
248
|
+
The tool result diff is rendered over cells, not raw JSON, and excludes the
|
|
249
|
+
spurious nbformat-version line.
|
|
250
|
+
- `exec_notebook` — `nbconvert --execute --inplace` in a subprocess, 300 s default
|
|
251
|
+
(`tools.notebook_timeout_s`), run against a **copy** and moved back, so a crash
|
|
252
|
+
mid-write cannot corrupt a tracked notebook. Outputs are read back from the
|
|
253
|
+
notebook, never from nbconvert's stdout.
|
|
254
|
+
- `allow_errors=True` means a failing cell does not stop the run; the tool reports
|
|
255
|
+
`N failing cell(s)` (nbconvert exits 0 either way, so the exit code is not the
|
|
256
|
+
verdict), extracts the last traceback, and never prints the whole log when a
|
|
257
|
+
traceback already explains the failure.
|
|
258
|
+
- FR-CTX-4 is enforced twice: only the last traceback and a 40-line stdout tail
|
|
259
|
+
enter context, **and** each line is capped at 300 chars, because
|
|
260
|
+
`print(x * 200)` is one line that would otherwise walk past a line-count budget.
|
|
261
|
+
Images and HTML become artifacts in `meta["outputs"]`, and never reach the
|
|
262
|
+
prompt as base64.
|
|
263
|
+
- Timeouts are detected from both paths: nbconvert's own `CellTimeoutError` and
|
|
264
|
+
coAgent's process-group kill.
|
|
265
|
+
- Shell escapes inside code cells (`!pip install …`) are gated by the same
|
|
266
|
+
approval matrix as `run_shell`, so `exec_notebook` is not a way around it.
|
|
267
|
+
- Phase gating: SURVEY may read a notebook, VERIFY may execute one, PLAN has no
|
|
268
|
+
tools, and only ACT may edit. `exec_notebook` counts as a write for commit
|
|
269
|
+
purposes, since it rewrites the notebook in place with outputs.
|
|
270
|
+
|
|
271
|
+
### Artifacts (`coagent.context.artifacts`, `coagent.tools.artifacts`)
|
|
272
|
+
|
|
273
|
+
The output of a data task is often a plot or a CSV, not a source file, and "done"
|
|
274
|
+
means the figure saved and visible (PRD US-3, G5). This is that pipeline.
|
|
275
|
+
|
|
276
|
+
- `ArtifactStore` applies the §3.5 size policy in code, not by convention:
|
|
277
|
+
under `artifacts.commit_under_mb` goes to `artifacts/<run-id>/` and is
|
|
278
|
+
committed; over it goes to Drive keyed by sha256 and only a pointer is
|
|
279
|
+
recorded; over 2 GB is never copied at all. **AT-7 is a test**: a 20 MB file
|
|
280
|
+
produces no bytes in the tree, only a manifest entry.
|
|
281
|
+
- `artifacts/<run-id>/manifest.json` records
|
|
282
|
+
`{path, sha256, bytes, store, created_by_step}` per entry, sorted by path, and
|
|
283
|
+
replaces an entry on re-run rather than duplicating it — so re-running a cell
|
|
284
|
+
does not make the manifest describe a file that no longer exists.
|
|
285
|
+
- Drive is optional and detected, never required (FR-BOOT-4). With no mount an
|
|
286
|
+
over-cap artifact is **not** silently dropped and not forced into git: it
|
|
287
|
+
becomes an honest pointer whose note says to mount Drive or raise the cap on
|
|
288
|
+
purpose. That is the graceful degradation the PRD asks for.
|
|
289
|
+
- Artifact names are untrusted input from cell ids and model arguments, so they
|
|
290
|
+
are sanitised into a single path segment: `../../etc/passwd` becomes
|
|
291
|
+
`passwd.txt`, and a missing extension is inferred from the MIME type.
|
|
292
|
+
- `save_artifact` — stores an existing file by `source` or inline `text`.
|
|
293
|
+
It refuses to invent content: an agent that can fabricate a plot it never drew
|
|
294
|
+
is worse than one that reports the cell produced nothing. Empty files are
|
|
295
|
+
rejected as a failed job, not saved as an artifact.
|
|
296
|
+
- `display_artifact` — renders image/HTML/text inline (FR-UX-4) through an
|
|
297
|
+
optional injected callback, so the notebook UI stays out of the tool layer.
|
|
298
|
+
Headless (CLI, tests) it references the file instead; a renderer that raises is
|
|
299
|
+
reported and never fails the step. With no path it renders the manifest.
|
|
300
|
+
- Notebook integration: `exec_notebook` now stores displayed images and HTML
|
|
301
|
+
automatically, named from the cell id, so re-running replaces the figure
|
|
302
|
+
instead of piling up `output-1.png`, `output-2.png`. Cell streams are not
|
|
303
|
+
artifacts — they are the tail, not the product.
|
|
304
|
+
- `save_artifact` joins the commit set, so the artifact and its manifest ride in
|
|
305
|
+
the same commit that produced them, and the `Coagent-Artifacts` trailer names
|
|
306
|
+
only bytes actually in that commit. A Drive pointer is never claimed as a path.
|
|
307
|
+
- `Coagent-Artifacts` is now populated end-to-end: `state.artifacts` ← store ←
|
|
308
|
+
the `save_artifact` / `exec_notebook` step, and the manifest is written even
|
|
309
|
+
when every artifact went to Drive or the run stopped early.
|
|
310
|
+
- `resume` reads `artifacts/<run>/manifest.json` from git and names the artifacts
|
|
311
|
+
in the briefing, because a fresh VM has the files but no memory of them
|
|
312
|
+
(§3.6). A malformed manifest is ignored rather than blocking a resume.
|
|
313
|
+
- The loop now shares one `FileSession` and one `ArtifactStore` across phases, so
|
|
314
|
+
a SURVEY read satisfies ACT's read-before-write guard and artifacts accumulate
|
|
315
|
+
for the whole session.
|
|
316
|
+
- `SafetyClass.UI` is now reachable: ACT, VERIFY, and REPORT may render an
|
|
317
|
+
artifact; SURVEY and PLAN may not.
|
|
318
|
+
|
|
319
|
+
### Notebook UI (`coagent.ui`)
|
|
320
|
+
The guaranteed surface (DESIGN §8.1): a chat cell that is good enough that the
|
|
321
|
+
product works without Gradio. Split in two on purpose — `render.py` is the
|
|
322
|
+
logic and is fully tested; `notebook.py` is plumbing.
|
|
323
|
+
|
|
324
|
+
- `render.Transcript` folds the loop's event stream into rows. Tool calls become
|
|
325
|
+
collapsed `<details>` blocks — one informative line closed, args and truncated
|
|
326
|
+
output open — so a 40-step run stays readable in a phone-width pane without
|
|
327
|
+
hiding the evidence. HTML in tool output is escaped.
|
|
328
|
+
- Headline rendering is per tool, because a collapsed row has to be useful on its
|
|
329
|
+
own: `edit_file src/loader.py +18 −3` counts real changed lines (diff headers
|
|
330
|
+
excluded), a read names the path and size, a shell call reports its status line.
|
|
331
|
+
- `render_status_line` is FR-UX-3 in one line: model, context, GPU, session, and
|
|
332
|
+
budget. One line because it sits at the top of every repaint.
|
|
333
|
+
- Unknown events are rendered, not dropped, so a new loop event appears in the
|
|
334
|
+
transcript the day it is added rather than the day this file learns of it.
|
|
335
|
+
- The row buffer is bounded; old rows leave the *view* but never the session log.
|
|
336
|
+
- `ChatCell` draws through `display_id` + `update` rather than
|
|
337
|
+
`clear_output(wait=True)`, which is what §8.1 asks for: the latter flickers and
|
|
338
|
+
scrolls the pane.
|
|
339
|
+
- Approvals block on a `threading.Event` a button callback sets — Colab delivers
|
|
340
|
+
widget messages on another thread, so the cell can wait without freezing. The
|
|
341
|
+
wait has a timeout and **denies** on expiry: a missing human is not consent,
|
|
342
|
+
consistent with the non-interactive default (§9.1).
|
|
343
|
+
- FR-UX-7 (reconnect-safe): `cell.command("approve"|"deny"|"stop"|"resume")`
|
|
344
|
+
resolves a pending decision from a plain text cell, so a lost widget cannot
|
|
345
|
+
strand a session.
|
|
346
|
+
- `ChatCell.render_artifact` draws images and HTML below the transcript, keeping
|
|
347
|
+
the log on screen (FR-UX-4). A renderer that raises is reported, never fatal.
|
|
348
|
+
- Everything degrades: no IPython or ipywidgets and the cell still records the
|
|
349
|
+
transcript, and `coAgent()` outside a live kernel stays headless.
|
|
350
|
+
- `coAgent()` now wires the cell automatically in a notebook — `on_event`,
|
|
351
|
+
`approver`, and `renderer` — while explicit arguments win and
|
|
352
|
+
`[ui] mode = "headless"` turns it off. That makes the documented two-line
|
|
353
|
+
notebook API (§8.2) actually true.
|
|
354
|
+
- `agent.show_log()` — the last N steps with the committed diff (§8.2), read from
|
|
355
|
+
the durable JSONL log so it still works after a resume.
|
|
356
|
+
- The loop's `tool` event now carries `path`, so the transcript can name the file
|
|
357
|
+
in a collapsed row without re-parsing tool output.
|
|
358
|
+
|
|
359
|
+
### Optional Gradio UI (`coagent.ui.gradio_app`)
|
|
360
|
+
DESIGN §8.3 calls this a *nicety*, so it is built as one: optional, guarded, and
|
|
361
|
+
reusing the same `Transcript` the notebook draws. Nothing in the loop requires it.
|
|
362
|
+
|
|
363
|
+
- `GradioApp` — a status line, a markdown transcript, an approval banner with
|
|
364
|
+
Approve/Deny, and a prompt box, on `ui.gradio_port` (default 7860).
|
|
365
|
+
- Served **loopback only** (`server_name="127.0.0.1"`, `share=False`). In Colab
|
|
366
|
+
the tab reaches it through `serve_kernel_port_as_window`, so there is no reason
|
|
367
|
+
to bind a public interface; ngrok and `share=True` stay out of scope.
|
|
368
|
+
- `pip install 'coagent[gradio]'`, or `coagent[all]`. Importing `coagent.ui`
|
|
369
|
+
without Gradio is fine — the widget modules load lazily and `GradioApp()` raises
|
|
370
|
+
`GradioUnavailable` with the exact install command.
|
|
371
|
+
- Approvals reuse the notebook contract: a `threading.Event` the loop blocks on,
|
|
372
|
+
a timeout, and **deny on expiry**, because a missing human is not consent (§9.1).
|
|
373
|
+
- The pending command is HTML-escaped before it reaches the banner: it is model
|
|
374
|
+
output, and the banner is markup.
|
|
375
|
+
- `render_artifact` records the path in the transcript rather than pretending
|
|
376
|
+
Gradio can display an artifact inline; the notebook cell remains the place
|
|
377
|
+
artifacts are actually shown. The two surfaces are honest about the difference.
|
|
378
|
+
- Tests substitute a minimal stub when real Gradio is unavailable (it does not
|
|
379
|
+
install on Python 3.15), so the approval queue, hook wiring, HTML escaping, and
|
|
380
|
+
launch kwargs are exercised rather than skipped.
|
|
381
|
+
|
|
382
|
+
### Tests
|
|
383
|
+
|
|
384
|
+
475 unit and integration tests, CPU-only — no GPU and no network required.
|
|
385
|
+
|
|
386
|
+
### Bootstrap (FR-BOOT-1/2, FR-DIS-2)
|
|
387
|
+
|
|
388
|
+
- `notebooks/bootstrap.ipynb` — the Open-in-Colab landing page. A preflight cell,
|
|
389
|
+
then **exactly three lines** (install, construct, chat), then what the three
|
|
390
|
+
lines hide: the log, the reviewable branch, the resume-after-death cell, and
|
|
391
|
+
the text-command fallback. Tests assert the three-line limit and the
|
|
392
|
+
preflight-before-agent ordering so the landing page cannot silently rot.
|
|
393
|
+
- **FR-BOOT-2 is now enforced, not just printed.** `coAgent.__init__` runs the
|
|
394
|
+
T4 preflight *before* constructing the backend and raises `HardwareError` with
|
|
395
|
+
an actionable message, so a CPU runtime fails in a second instead of after a
|
|
396
|
+
four-gigabyte download. `allow_unsupported=True` (and `coagent run
|
|
397
|
+
--allow-unsupported`) is documented in the error itself. An injected backend
|
|
398
|
+
skips the check — that caller has already decided about hardware.
|
|
399
|
+
- `coAgent(repo="https://…")` now clones into `coagent-workspace/` instead of
|
|
400
|
+
failing as a bad path, so the documented one-liner is true. Shallow, once:
|
|
401
|
+
an existing `.git` is reused rather than re-cloned. `/content` on Colab, the
|
|
402
|
+
cwd elsewhere.
|
|
403
|
+
- **Fixed: a run no longer leaves the tree dirty.** `ArtifactStore.flush()`
|
|
404
|
+
wrote an empty `artifacts/<session>/manifest.json` that nothing committed, and
|
|
405
|
+
the next session then refused to start with `DirtyTreeError` — the resume path
|
|
406
|
+
was broken by a file that recorded nothing. An empty manifest is now not
|
|
407
|
+
written; a real one still is.
|
|
408
|
+
Includes end-to-end runs on a real git repository: a scripted model drives
|
|
409
|
+
SURVEY → PLAN → ACT → VERIFY → commit with trailers, and separate tests prove
|
|
410
|
+
the dirty-tree refusal, plan rejection before any write, honest verify trailers,
|
|
411
|
+
and clean budget exhaustion. Notebook execution tests run against a real Jupyter
|
|
412
|
+
kernel and skip cleanly when none is installed.
|
|
413
|
+
|
|
414
|
+
### Release notes — 0.1.0
|
|
415
|
+
|
|
416
|
+
First release: milestone M1. The loop is proven end-to-end on `FakeBackend` in
|
|
417
|
+
`notebooks/bootstrap.ipynb`, and the full v0.1 tool surface from `DESIGN.md` is
|
|
418
|
+
built — including `run_tests`/`tools/pyt.py`, which turns pytest output into a
|
|
419
|
+
digest instead of a dump. Every module in `DESIGN.md` §11 exists; the remaining
|
|
420
|
+
work before a T4 run is breadth of behaviour on a real GPU, not missing code.
|
|
421
|
+
|
|
422
|
+
Verification performed for this release: 476 tests pass with no skips, and the
|
|
423
|
+
built wheel installs into a clean venv where `coagent init`, `config`, `tools`,
|
|
424
|
+
and `run --dry-run` all succeed outside the source tree.
|
|
425
|
+
|
|
426
|
+
Not yet proven: a real 7B NF4 load and a live loop run on a free T4 (acceptance
|
|
427
|
+
test AT-1). That requires the Colab runtime and is the first post-release check.
|