chipgraph 0.1.0__py3-none-any.whl
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.
- chipgraph/__init__.py +7 -0
- chipgraph/adapters/__init__.py +1 -0
- chipgraph/adapters/format/__init__.py +6 -0
- chipgraph/adapters/format/__main__.py +16 -0
- chipgraph/adapters/format/_yaml_lines.py +54 -0
- chipgraph/adapters/format/chip_yaml.py +323 -0
- chipgraph/adapters/format/qsoc_contract.py +246 -0
- chipgraph/adapters/llm/__init__.py +58 -0
- chipgraph/adapters/llm/_budget.py +173 -0
- chipgraph/adapters/llm/_errors.py +50 -0
- chipgraph/adapters/llm/_nda.py +30 -0
- chipgraph/adapters/llm/_retry.py +85 -0
- chipgraph/adapters/llm/anthropic.py +247 -0
- chipgraph/adapters/llm/decide_backend.py +179 -0
- chipgraph/adapters/llm/fake.py +87 -0
- chipgraph/adapters/llm/guard.py +68 -0
- chipgraph/adapters/parser/__init__.py +22 -0
- chipgraph/adapters/parser/cocotb.py +304 -0
- chipgraph/adapters/parser/generic_regex.py +75 -0
- chipgraph/adapters/parser/verible.py +55 -0
- chipgraph/adapters/parser/verilator.py +46 -0
- chipgraph/adapters/review/__init__.py +7 -0
- chipgraph/adapters/review/file.py +123 -0
- chipgraph/adapters/review/github.py +886 -0
- chipgraph/adapters/runner/__init__.py +7 -0
- chipgraph/adapters/runner/local.py +122 -0
- chipgraph/adapters/runtime/__init__.py +5 -0
- chipgraph/adapters/runtime/claude_code/__init__.py +24 -0
- chipgraph/adapters/runtime/claude_code/agents.py +158 -0
- chipgraph/adapters/runtime/claude_code/decisions.py +280 -0
- chipgraph/adapters/runtime/claude_code/guarded.py +310 -0
- chipgraph/adapters/runtime/claude_code/isolation.py +482 -0
- chipgraph/adapters/runtime/claude_code/loop.py +342 -0
- chipgraph/adapters/runtime/claude_code/previous.py +419 -0
- chipgraph/adapters/runtime/claude_code/review.py +492 -0
- chipgraph/adapters/runtime/claude_code/runtime.py +192 -0
- chipgraph/adapters/runtime/claude_code/service.py +2285 -0
- chipgraph/adapters/tool/__init__.py +7 -0
- chipgraph/adapters/tool/cmd.py +229 -0
- chipgraph/adapters/tool/edalize.py +773 -0
- chipgraph/adapters/tool/filelist.py +259 -0
- chipgraph/adapters/tool/pyslang.py +485 -0
- chipgraph/adapters/vcs/__init__.py +1 -0
- chipgraph/adapters/vcs/git.py +521 -0
- chipgraph/app/__init__.py +10 -0
- chipgraph/app/baseline.py +339 -0
- chipgraph/app/build.py +332 -0
- chipgraph/app/checks.py +202 -0
- chipgraph/app/context.py +191 -0
- chipgraph/app/errors.py +11 -0
- chipgraph/app/executors.py +79 -0
- chipgraph/app/findings.py +203 -0
- chipgraph/app/ingest.py +523 -0
- chipgraph/checks/__init__.py +52 -0
- chipgraph/checks/_cdc_analyze.py +272 -0
- chipgraph/checks/_cdc_elaborate.py +455 -0
- chipgraph/checks/_common.py +268 -0
- chipgraph/checks/_cross.py +96 -0
- chipgraph/checks/_model.py +64 -0
- chipgraph/checks/_naming_pyslang.py +235 -0
- chipgraph/checks/_naming_rules.py +127 -0
- chipgraph/checks/_xref_syntax.py +260 -0
- chipgraph/checks/cdc_struct.py +235 -0
- chipgraph/checks/connect.py +353 -0
- chipgraph/checks/cross_chip.py +454 -0
- chipgraph/checks/duplicate.py +153 -0
- chipgraph/checks/filelist.py +323 -0
- chipgraph/checks/format.py +268 -0
- chipgraph/checks/generated.py +151 -0
- chipgraph/checks/hardcode.py +233 -0
- chipgraph/checks/layout.py +155 -0
- chipgraph/checks/naming.py +311 -0
- chipgraph/checks/plan_check.py +901 -0
- chipgraph/checks/ports_diff.py +203 -0
- chipgraph/checks/spec_schema.py +277 -0
- chipgraph/checks/style.py +910 -0
- chipgraph/checks/tb_static.py +599 -0
- chipgraph/checks/trace.py +224 -0
- chipgraph/checks/vendor_guard.py +369 -0
- chipgraph/checks/wrap_check.py +486 -0
- chipgraph/cli/__init__.py +1688 -0
- chipgraph/cli/next_steps.py +137 -0
- chipgraph/core/__init__.py +1 -0
- chipgraph/core/config/__init__.py +93 -0
- chipgraph/core/config/defaults.py +14 -0
- chipgraph/core/config/errors.py +32 -0
- chipgraph/core/config/loader.py +766 -0
- chipgraph/core/config/models.py +845 -0
- chipgraph/core/config/templates.py +340 -0
- chipgraph/core/contracts/__init__.py +75 -0
- chipgraph/core/contracts/_paths.py +26 -0
- chipgraph/core/contracts/agent.py +49 -0
- chipgraph/core/contracts/artifact.py +68 -0
- chipgraph/core/contracts/check.py +76 -0
- chipgraph/core/contracts/event.py +70 -0
- chipgraph/core/contracts/export.py +128 -0
- chipgraph/core/contracts/finding.py +171 -0
- chipgraph/core/contracts/gate.py +50 -0
- chipgraph/core/contracts/rule.py +177 -0
- chipgraph/core/contracts/types.py +45 -0
- chipgraph/core/engine/__init__.py +70 -0
- chipgraph/core/engine/agent_rule.py +1362 -0
- chipgraph/core/engine/baseline.py +283 -0
- chipgraph/core/engine/decide.py +462 -0
- chipgraph/core/engine/fanout.py +1358 -0
- chipgraph/core/engine/gate.py +170 -0
- chipgraph/core/engine/graph.py +647 -0
- chipgraph/core/engine/records.py +60 -0
- chipgraph/core/engine/rules.py +92 -0
- chipgraph/core/engine/scheduler.py +637 -0
- chipgraph/core/engine/select.py +1239 -0
- chipgraph/core/engine/targets.py +342 -0
- chipgraph/core/model/__init__.py +98 -0
- chipgraph/core/model/entities.py +244 -0
- chipgraph/core/model/export.py +16 -0
- chipgraph/core/model/ingest.py +693 -0
- chipgraph/core/model/json_value.py +4 -0
- chipgraph/core/model/keys.py +95 -0
- chipgraph/core/model/model.py +153 -0
- chipgraph/core/model/provenance.py +27 -0
- chipgraph/core/model/query.py +583 -0
- chipgraph/core/model/registry.py +80 -0
- chipgraph/core/model/relations.py +61 -0
- chipgraph/core/model/store.py +279 -0
- chipgraph/core/plugin_api/__init__.py +81 -0
- chipgraph/core/plugin_api/local.py +167 -0
- chipgraph/core/plugin_api/pack.py +159 -0
- chipgraph/core/plugin_api/protocols.py +177 -0
- chipgraph/core/plugin_api/registry.py +111 -0
- chipgraph/core/plugin_api/types.py +94 -0
- chipgraph/core/runtime/__init__.py +41 -0
- chipgraph/core/runtime/executor.py +79 -0
- chipgraph/core/runtime/queue.py +565 -0
- chipgraph/core/runtime/roles/__init__.py +91 -0
- chipgraph/core/runtime/roles/data/author.md +41 -0
- chipgraph/core/runtime/roles/data/critic.md +56 -0
- chipgraph/core/runtime/roles/data/planner.md +38 -0
- chipgraph/core/runtime/roles/data/researcher.md +37 -0
- chipgraph/core/runtime/roles/data/rtl-author.md +64 -0
- chipgraph/core/runtime/roles/data/tb-author.md +48 -0
- chipgraph/core/runtime/roles/data/triage.md +34 -0
- chipgraph/core/runtime/roles/policy.py +299 -0
- chipgraph/core/runtime/roles/registry.py +52 -0
- chipgraph/core/runtime/roles/review.py +275 -0
- chipgraph/core/runtime/roles/schema.py +82 -0
- chipgraph/core/runtime/roles/skills.py +134 -0
- chipgraph/core/runtime/roles/spec.py +232 -0
- chipgraph/core/state/__init__.py +1 -0
- chipgraph/core/state/artifacts.py +156 -0
- chipgraph/core/state/backend.py +117 -0
- chipgraph/core/state/findings.py +151 -0
- chipgraph/core/state/handoff.py +411 -0
- chipgraph/core/state/idempotency.py +59 -0
- chipgraph/core/state/journal.py +238 -0
- chipgraph/core/state/layout.py +82 -0
- chipgraph/core/state/lock.py +195 -0
- chipgraph/core/state/trace.py +225 -0
- chipgraph/learn/__init__.py +96 -0
- chipgraph/learn/draft.py +187 -0
- chipgraph/learn/infer.py +580 -0
- chipgraph/learn/models.py +145 -0
- chipgraph/learn/try_run.py +228 -0
- chipgraph/mcp/__init__.py +12 -0
- chipgraph/mcp/model_tools.py +167 -0
- chipgraph/mcp/server.py +787 -0
- chipgraph/orgs/qnsc/naming-v1.yml +113 -0
- chipgraph/orgs/qnsc/profile.yml +5 -0
- chipgraph/packs/__init__.py +8 -0
- chipgraph/packs/assist/__init__.py +16 -0
- chipgraph/packs/assist/ask/__init__.py +58 -0
- chipgraph/packs/assist/ask/_db.py +57 -0
- chipgraph/packs/assist/ask/_project.py +230 -0
- chipgraph/packs/assist/ask/answer.py +266 -0
- chipgraph/packs/assist/ask/check.py +182 -0
- chipgraph/packs/assist/ask/contract.py +115 -0
- chipgraph/packs/assist/ask/documents.py +157 -0
- chipgraph/packs/assist/ask/fts.py +80 -0
- chipgraph/packs/assist/ask/retrieve.py +331 -0
- chipgraph/packs/assist/audit/__init__.py +383 -0
- chipgraph/packs/assist/pack.yml +13 -0
- chipgraph/packs/assist/triage/__init__.py +64 -0
- chipgraph/packs/assist/triage/contract.py +94 -0
- chipgraph/packs/assist/triage/parse.py +371 -0
- chipgraph/packs/assist/triage/question.py +384 -0
- chipgraph/packs/assist/triage/report.py +160 -0
- chipgraph/packs/assist/triage/rules.py +447 -0
- chipgraph/packs/assist/triage/run.py +224 -0
- chipgraph/packs/digital_rtl/__init__.py +19 -0
- chipgraph/packs/digital_rtl/layout.py +144 -0
- chipgraph/packs/digital_rtl/pack.yml +25 -0
- chipgraph/packs/digital_rtl/plan/__init__.py +34 -0
- chipgraph/packs/digital_rtl/plan/__main__.py +16 -0
- chipgraph/packs/digital_rtl/plan/context.py +82 -0
- chipgraph/packs/digital_rtl/plan/expand.py +94 -0
- chipgraph/packs/digital_rtl/plan/model.py +150 -0
- chipgraph/packs/digital_rtl/plan/resolver.py +400 -0
- chipgraph/packs/digital_rtl/plan/show.py +176 -0
- chipgraph/packs/digital_rtl/pr/__init__.py +11 -0
- chipgraph/packs/digital_rtl/pr/body.py +165 -0
- chipgraph/packs/digital_rtl/pr/gate.py +110 -0
- chipgraph/packs/digital_rtl/pr/open.py +397 -0
- chipgraph/packs/digital_rtl/rtl/__init__.py +9 -0
- chipgraph/packs/digital_rtl/rtl/context.py +272 -0
- chipgraph/packs/digital_rtl/rules/open_pr.yml +30 -0
- chipgraph/packs/digital_rtl/rules/plan.yml +28 -0
- chipgraph/packs/digital_rtl/rules/plan_expand.yml +29 -0
- chipgraph/packs/digital_rtl/rules/pr_gate.yml +27 -0
- chipgraph/packs/digital_rtl/rules/review.yml +31 -0
- chipgraph/packs/digital_rtl/rules/rtl_module.yml +53 -0
- chipgraph/packs/digital_rtl/skills/lang-sv-rtl.md +58 -0
- chipgraph/packs/digital_rtl/skills/org-naming.md +29 -0
- chipgraph/packs/digital_rtl/skills/plan-modules.md +63 -0
- chipgraph/packs/digital_rtl/skills/review-diff.md +41 -0
- chipgraph/packs/digital_rtl/targets/plan.yml +11 -0
- chipgraph/packs/digital_rtl/targets/rtl.yml +20 -0
- chipgraph/packs/digital_rtl/wrapper/__init__.py +24 -0
- chipgraph/packs/digital_rtl/wrapper/common.py +171 -0
- chipgraph/packs/digital_rtl/wrapper/context.py +222 -0
- chipgraph/packs/digital_rtl/wrapper/emacs.py +626 -0
- chipgraph/packs/digital_rtl/wrapper/expansion.py +304 -0
- chipgraph/packs/digital_rtl/wrapper/lisp.py +504 -0
- chipgraph/packs/digital_rtl/wrapper/resolver.py +60 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper.yml +41 -0
- chipgraph/packs/digital_rtl/wrapper/rules/wrapper_gen.yml +35 -0
- chipgraph/packs/digital_rtl/wrapper/skills/wrapper-auto.md +120 -0
- chipgraph/packs/dv/__init__.py +15 -0
- chipgraph/packs/dv/declaration.py +392 -0
- chipgraph/packs/dv/interface.py +596 -0
- chipgraph/packs/dv/pack.yml +14 -0
- chipgraph/packs/dv/rules/tb_module.yml +42 -0
- chipgraph/packs/dv/skills/dv/cocotb.md +72 -0
- chipgraph/packs/spec_core/__init__.py +9 -0
- chipgraph/packs/spec_core/extract/__init__.py +12 -0
- chipgraph/packs/spec_core/extract/_markdown.py +371 -0
- chipgraph/packs/spec_core/extract/_text.py +116 -0
- chipgraph/packs/spec_core/extract/mas.py +975 -0
- chipgraph/packs/spec_core/gen/__init__.py +11 -0
- chipgraph/packs/spec_core/gen/agents_md/__init__.py +148 -0
- chipgraph/packs/spec_core/gen/agents_md/_context.py +239 -0
- chipgraph/packs/spec_core/gen/agents_md/_markers.py +131 -0
- chipgraph/packs/spec_core/gen/agents_md/agents_md.md.j2 +178 -0
- chipgraph/packs/spec_core/gen/diagram/__init__.py +110 -0
- chipgraph/packs/spec_core/gen/diagram/__main__.py +44 -0
- chipgraph/packs/spec_core/gen/diagram/_diagrams.py +403 -0
- chipgraph/packs/spec_core/gen/diagram/_scene.py +163 -0
- chipgraph/packs/spec_core/pack.yml +6 -0
- chipgraph/py.typed +0 -0
- chipgraph-0.1.0.dist-info/METADATA +53 -0
- chipgraph-0.1.0.dist-info/RECORD +252 -0
- chipgraph-0.1.0.dist-info/WHEEL +4 -0
- chipgraph-0.1.0.dist-info/entry_points.txt +61 -0
- chipgraph-0.1.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
"""The M2-02a agent rule loop, one submitted attempt at a time (DESIGN.md 5.2).
|
|
2
|
+
|
|
3
|
+
In an in-process runtime `AgentRuleExecutor` runs the whole loop. In runtime
|
|
4
|
+
`claude-code` the user's Claude Code session does each attempt and `submit` checks it,
|
|
5
|
+
so this module applies the same pieces (`chipgraph.core.engine.agent_rule`) to one
|
|
6
|
+
attempt and keeps the loop's count on the task record (`TaskBudgetState`):
|
|
7
|
+
|
|
8
|
+
- checks: `attempt_checks` (inputs, outputs, writes) and the rule's checks, each once;
|
|
9
|
+
- label: `FailureClassifier` over the default `LabelMap`, the one the in-process loop
|
|
10
|
+
uses (deterministic rules only: no model is asked here);
|
|
11
|
+
- budget: `AttemptBudget.for_rule`, restored from the record. An `infra` failure (a
|
|
12
|
+
check that errored, a tool that cannot run) uses no try, and at most
|
|
13
|
+
`max_infra_retries` (2, as in process) of them are retried; two failed tries in a
|
|
14
|
+
row with the same outputs and failures stop the task early (`stagnation`). Tokens and
|
|
15
|
+
cost are not known per task in this runtime (the session reports them only for the
|
|
16
|
+
whole run), so the token and cost caps of the in-process loop do not apply here;
|
|
17
|
+
- redo: `render_feedback`, the bounded redo instruction for the next try. For a role
|
|
18
|
+
that must not see some artifacts (the testbench Author and the RTL, the RTL Author
|
|
19
|
+
and the testbench), what the answer and the redo text show of the failures goes
|
|
20
|
+
through `FeedbackFilter` first: issues in a denied file are dropped, denied paths in
|
|
21
|
+
messages become `<rtl>` (`<tb>`), source excerpt lines are cut, and the redo text says
|
|
22
|
+
that something was withheld (`FeedbackFilter.note`). The label is still taken from
|
|
23
|
+
the full results.
|
|
24
|
+
|
|
25
|
+
`max_dispatches` (tries plus infra retries) is the hard bound on how often one task is
|
|
26
|
+
handed out in a budget cycle: every submit either uses a try or an infra retry, and
|
|
27
|
+
the loop stops when either runs out, so the bound is never reached by a live task.
|
|
28
|
+
`TaskQueue.dispatch` and `next_task` both refuse to go past it.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from collections.abc import Mapping, Sequence
|
|
34
|
+
from dataclasses import dataclass
|
|
35
|
+
from typing import Any
|
|
36
|
+
|
|
37
|
+
from chipgraph.core.contracts import AgentResult, CheckResult, Issue, RuleSpec
|
|
38
|
+
from chipgraph.core.contracts.types import FailureLabel, ModelTier
|
|
39
|
+
from chipgraph.core.engine.agent_rule import (
|
|
40
|
+
DEFAULT_LABEL_MAP,
|
|
41
|
+
DEFAULT_MAX_INFRA_RETRIES,
|
|
42
|
+
AttemptBudget,
|
|
43
|
+
BudgetState,
|
|
44
|
+
FailureClassifier,
|
|
45
|
+
FeedbackFilter,
|
|
46
|
+
failing,
|
|
47
|
+
render_feedback,
|
|
48
|
+
)
|
|
49
|
+
from chipgraph.core.runtime.queue import AgentTaskRecord, BudgetStop, TaskBudgetState, TaskStatus
|
|
50
|
+
|
|
51
|
+
MAX_INFRA_RETRIES = DEFAULT_MAX_INFRA_RETRIES
|
|
52
|
+
"""Submits of one task that may fail on infra without using a try (as in process)."""
|
|
53
|
+
|
|
54
|
+
REVIEW_CHECK = "agent.review"
|
|
55
|
+
"""Synthetic check id of a review task's reply (invalid JSON, schema, evidence)."""
|
|
56
|
+
|
|
57
|
+
MAX_FAILED_CHECKS = 10
|
|
58
|
+
MAX_ISSUES_PER_CHECK = 8
|
|
59
|
+
MAX_MSG_CHARS = 300
|
|
60
|
+
REDO_MAX_CHARS = 6000
|
|
61
|
+
|
|
62
|
+
_CLASSIFIER = FailureClassifier(label_map=DEFAULT_LABEL_MAP)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def max_dispatches(tries: int, max_infra_retries: int = MAX_INFRA_RETRIES) -> int:
|
|
66
|
+
"""The most dispatches of one task in a budget cycle: its tries plus infra retries."""
|
|
67
|
+
return tries + max_infra_retries
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def dispatch_limit(record: AgentTaskRecord) -> int:
|
|
71
|
+
"""The record's dispatch bound (one from before it was stored: the default)."""
|
|
72
|
+
if record.max_dispatches is not None:
|
|
73
|
+
return record.max_dispatches
|
|
74
|
+
return max_dispatches(record.tries)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@dataclass(frozen=True)
|
|
78
|
+
class Verdict:
|
|
79
|
+
"""What one submitted attempt comes to: the record's next status and budget state."""
|
|
80
|
+
|
|
81
|
+
status: TaskStatus
|
|
82
|
+
label: FailureLabel | None
|
|
83
|
+
counted: bool
|
|
84
|
+
stop: BudgetStop | None
|
|
85
|
+
tier: ModelTier
|
|
86
|
+
state: TaskBudgetState
|
|
87
|
+
tries: int
|
|
88
|
+
failed: tuple[CheckResult, ...]
|
|
89
|
+
questions: tuple[str, ...] = ()
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def accepted(self) -> bool:
|
|
93
|
+
return self.status == "accepted"
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def tries_left(self) -> int:
|
|
97
|
+
return max(self.tries - self.state.tries_used, 0)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _cut(text: str, limit: int) -> str:
|
|
101
|
+
return text if len(text) <= limit else text[: max(limit - 1, 0)] + "…"
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def issue_line(issue: Issue) -> str:
|
|
105
|
+
"""`file:line: [rule] msg`, as the redo instruction writes an issue."""
|
|
106
|
+
where = issue.file or ""
|
|
107
|
+
if where and issue.line is not None:
|
|
108
|
+
where = f"{where}:{issue.line}"
|
|
109
|
+
rule = f"[{issue.rule}] " if issue.rule else ""
|
|
110
|
+
return f"{where + ': ' if where else ''}{rule}{issue.msg}".strip()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _errors(result: CheckResult) -> list[Issue]:
|
|
114
|
+
return [i for i in result.issues if i.severity == "error"] or list(result.issues)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def failure_line(result: CheckResult) -> str:
|
|
118
|
+
"""One line for a failing check: its id, status and first issue (or log tail)."""
|
|
119
|
+
issues = _errors(result)
|
|
120
|
+
first = _cut(issue_line(issues[0]), 200) if issues else _cut(result.log_tail.strip(), 200)
|
|
121
|
+
more = f" (+{len(issues) - 1} more)" if len(issues) > 1 else ""
|
|
122
|
+
return f"{result.check_id}: {result.status}" + (f": {first}{more}" if first else "")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def failed_checks(results: Sequence[CheckResult]) -> list[dict[str, Any]]:
|
|
126
|
+
"""The failing checks for `submit`'s answer: id, status, issues (capped), and the log
|
|
127
|
+
tail when no error issue explains the failure."""
|
|
128
|
+
shown: list[dict[str, Any]] = []
|
|
129
|
+
for result in failing(results)[:MAX_FAILED_CHECKS]:
|
|
130
|
+
issues = _errors(result)
|
|
131
|
+
entry: dict[str, Any] = {
|
|
132
|
+
"check_id": result.check_id,
|
|
133
|
+
"status": result.status,
|
|
134
|
+
"issues": [
|
|
135
|
+
{
|
|
136
|
+
"at": f"{i.file}:{i.line}" if i.file and i.line else i.file,
|
|
137
|
+
"rule": i.rule,
|
|
138
|
+
"msg": _cut(i.msg, MAX_MSG_CHARS),
|
|
139
|
+
}
|
|
140
|
+
for i in issues[:MAX_ISSUES_PER_CHECK]
|
|
141
|
+
],
|
|
142
|
+
}
|
|
143
|
+
if len(issues) > MAX_ISSUES_PER_CHECK:
|
|
144
|
+
entry["more_issues"] = len(issues) - MAX_ISSUES_PER_CHECK
|
|
145
|
+
if not any(i.severity == "error" for i in issues) and result.log_tail.strip():
|
|
146
|
+
entry["log_tail"] = result.log_tail.strip()[-600:]
|
|
147
|
+
shown.append(entry)
|
|
148
|
+
return shown
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def review_result(problems: Sequence[str]) -> CheckResult:
|
|
152
|
+
"""A review task's reply as a check: `fail` with one issue per problem."""
|
|
153
|
+
return CheckResult(
|
|
154
|
+
check_id=REVIEW_CHECK,
|
|
155
|
+
status="fail" if problems else "pass",
|
|
156
|
+
issues=tuple(Issue(rule="review.invalid", msg=p) for p in problems),
|
|
157
|
+
duration_s=0.0,
|
|
158
|
+
idempotency_key=f"{REVIEW_CHECK}:{len(problems)}",
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _budget(rule: RuleSpec, prior: TaskBudgetState) -> AttemptBudget:
|
|
163
|
+
budget = AttemptBudget.for_rule(rule)
|
|
164
|
+
budget.state = BudgetState(
|
|
165
|
+
tries_used=prior.tries_used,
|
|
166
|
+
failed_tries=prior.failed_tries,
|
|
167
|
+
infra_failures=prior.infra_failures,
|
|
168
|
+
last_signature=prior.last_signature,
|
|
169
|
+
stagnant=prior.stagnant,
|
|
170
|
+
)
|
|
171
|
+
return budget
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
async def judge(
|
|
175
|
+
rule: RuleSpec,
|
|
176
|
+
record: AgentTaskRecord,
|
|
177
|
+
results: Sequence[CheckResult],
|
|
178
|
+
agent: AgentResult,
|
|
179
|
+
output_hashes: Mapping[str, str],
|
|
180
|
+
*,
|
|
181
|
+
max_infra_retries: int = MAX_INFRA_RETRIES,
|
|
182
|
+
shown: FeedbackFilter | None = None,
|
|
183
|
+
) -> Verdict:
|
|
184
|
+
"""Count one submitted attempt against the task's budget and say what comes next.
|
|
185
|
+
|
|
186
|
+
`results` are every check of the attempt (the engine's and the rule's);
|
|
187
|
+
`output_hashes` the task's outputs as the attempt left them (for stagnation).
|
|
188
|
+
`shown` filters what the verdict's failures, failure lines, questions and redo text
|
|
189
|
+
show of `results` (see the module docstring); the label and the budget use them all.
|
|
190
|
+
"""
|
|
191
|
+
prior = record.state
|
|
192
|
+
budget = _budget(rule, prior)
|
|
193
|
+
tier = record.current_tier
|
|
194
|
+
bad = failing(results)
|
|
195
|
+
common: dict[str, Any] = {"dispatches": prior.dispatches, "tier": tier}
|
|
196
|
+
|
|
197
|
+
def state(**update: Any) -> TaskBudgetState:
|
|
198
|
+
s = budget.state
|
|
199
|
+
return TaskBudgetState(
|
|
200
|
+
tries_used=s.tries_used,
|
|
201
|
+
failed_tries=s.failed_tries,
|
|
202
|
+
infra_failures=s.infra_failures,
|
|
203
|
+
last_signature=s.last_signature,
|
|
204
|
+
stagnant=s.stagnant,
|
|
205
|
+
output_hashes=dict(output_hashes),
|
|
206
|
+
**common,
|
|
207
|
+
**update,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if not bad:
|
|
211
|
+
budget.record_pass()
|
|
212
|
+
return Verdict(
|
|
213
|
+
status="accepted",
|
|
214
|
+
label=None,
|
|
215
|
+
counted=True,
|
|
216
|
+
stop=None,
|
|
217
|
+
tier=tier,
|
|
218
|
+
state=state(),
|
|
219
|
+
tries=budget.tries,
|
|
220
|
+
failed=(),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
label = await _CLASSIFIER.classify(results, agent)
|
|
224
|
+
visible: Sequence[CheckResult] = results
|
|
225
|
+
withheld = 0
|
|
226
|
+
note = ""
|
|
227
|
+
if shown is not None:
|
|
228
|
+
visible, withheld = shown.apply(results)
|
|
229
|
+
note = shown.note
|
|
230
|
+
bad_shown = failing(visible)
|
|
231
|
+
lines = tuple(failure_line(r) for r in bad_shown[:MAX_FAILED_CHECKS])
|
|
232
|
+
ids = tuple(r.check_id for r in bad)
|
|
233
|
+
stop: BudgetStop | None = None
|
|
234
|
+
questions: tuple[str, ...] = ()
|
|
235
|
+
if label == "infra":
|
|
236
|
+
budget.record_infra()
|
|
237
|
+
counted = False
|
|
238
|
+
if budget.state.infra_failures > max_infra_retries:
|
|
239
|
+
stop = "infra"
|
|
240
|
+
redo = render_feedback(
|
|
241
|
+
visible, "infra", max_chars=1500, withheld=withheld, withheld_note=note
|
|
242
|
+
)
|
|
243
|
+
else:
|
|
244
|
+
budget.record_failure(results, output_hashes)
|
|
245
|
+
counted = True
|
|
246
|
+
if label == "planning":
|
|
247
|
+
stop = "planning"
|
|
248
|
+
questions = tuple(
|
|
249
|
+
issue_line(i) for r in bad_shown for i in r.issues if i.severity == "error"
|
|
250
|
+
)[:10] or ("the checks point at the spec or plan; a person must decide",)
|
|
251
|
+
else:
|
|
252
|
+
reason = budget.exhausted # tries | stagnation (no token or cost caps here)
|
|
253
|
+
if reason == "tries" or reason == "stagnation":
|
|
254
|
+
stop = reason
|
|
255
|
+
redo = render_feedback(
|
|
256
|
+
visible,
|
|
257
|
+
label,
|
|
258
|
+
attempt=budget.state.tries_used,
|
|
259
|
+
max_chars=REDO_MAX_CHARS,
|
|
260
|
+
withheld=withheld,
|
|
261
|
+
withheld_note=note,
|
|
262
|
+
)
|
|
263
|
+
status: TaskStatus
|
|
264
|
+
if stop == "planning":
|
|
265
|
+
status = "needs_human"
|
|
266
|
+
elif stop is not None:
|
|
267
|
+
status = "budget_exhausted"
|
|
268
|
+
else:
|
|
269
|
+
status = "rejected"
|
|
270
|
+
return Verdict(
|
|
271
|
+
status=status,
|
|
272
|
+
label=label,
|
|
273
|
+
counted=counted,
|
|
274
|
+
stop=stop,
|
|
275
|
+
tier=tier,
|
|
276
|
+
state=state(label=label, failed_checks=ids, failures=lines, redo=redo, stop=stop),
|
|
277
|
+
tries=budget.tries,
|
|
278
|
+
failed=bad_shown,
|
|
279
|
+
questions=questions,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# --- the stop, for the journal and HANDOFF.md ---------------------------------------------
|
|
284
|
+
|
|
285
|
+
_STOP_STATUS: dict[str, str] = {"planning": "needs_human"}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def stop_info(record: AgentTaskRecord, *, stopped_earlier: bool = False) -> dict[str, Any]:
|
|
289
|
+
"""The `agent` payload of a stopped task's `rule_fail` (what `AgentStopInfo` reads)."""
|
|
290
|
+
state = record.state
|
|
291
|
+
reason = state.stop
|
|
292
|
+
return {
|
|
293
|
+
"instance": record.task_id,
|
|
294
|
+
"status": _STOP_STATUS.get(reason or "", "budget_exhausted"),
|
|
295
|
+
"tries": state.tries_used,
|
|
296
|
+
"max_tries": record.tries,
|
|
297
|
+
"infra_failures": state.infra_failures,
|
|
298
|
+
"calls": state.dispatches,
|
|
299
|
+
"reason": reason,
|
|
300
|
+
"label": state.label,
|
|
301
|
+
"tier": state.tier,
|
|
302
|
+
"failed_checks": list(state.failed_checks),
|
|
303
|
+
"failures": list(state.failures),
|
|
304
|
+
"tokens": None,
|
|
305
|
+
"cost": None,
|
|
306
|
+
"stopped_earlier": stopped_earlier,
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
_STOP_HEAD = {
|
|
311
|
+
"tries": "budget exhausted (tries)",
|
|
312
|
+
"stagnation": "stopped early: two tries in a row left the same outputs and failures",
|
|
313
|
+
"infra": "stopped: infrastructure errors persisted",
|
|
314
|
+
"planning": "stopped: the checks point at the spec or plan",
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def stop_message(info: Mapping[str, Any]) -> str:
|
|
319
|
+
"""The `rule_fail` message of a stopped task, worded as the in-process loop's."""
|
|
320
|
+
tries = f"{info['tries']} of {info['max_tries']} tries"
|
|
321
|
+
infra = int(info["infra_failures"])
|
|
322
|
+
if infra:
|
|
323
|
+
tries += f", {infra} infra failure{'' if infra == 1 else 's'}"
|
|
324
|
+
failed = ", ".join(info["failed_checks"]) or "none"
|
|
325
|
+
head = _STOP_HEAD.get(str(info["reason"]), "stopped")
|
|
326
|
+
return f"agent {head} after {tries}; last failures [{info['label']}]: {failed}"
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
__all__ = [
|
|
330
|
+
"MAX_INFRA_RETRIES",
|
|
331
|
+
"REVIEW_CHECK",
|
|
332
|
+
"Verdict",
|
|
333
|
+
"dispatch_limit",
|
|
334
|
+
"failed_checks",
|
|
335
|
+
"failure_line",
|
|
336
|
+
"issue_line",
|
|
337
|
+
"judge",
|
|
338
|
+
"max_dispatches",
|
|
339
|
+
"review_result",
|
|
340
|
+
"stop_info",
|
|
341
|
+
"stop_message",
|
|
342
|
+
]
|