@zalom/plastic 2.0.0-alpha.26 → 2.0.0-alpha.27

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.
package/README.md CHANGED
@@ -156,9 +156,9 @@ plan a machine can build from exactly. Read
156
156
  or autonomous delivery by an agent team when you ask for it.
157
157
  - Two advisor agents ship for the hard problems: one-way doors, plans, adversarial
158
158
  review, deadlocks. Summon one deliberately, state the effort and the answer shape in the brief; nobody's
159
- main session is ever touched. Faux Fable (recommended) is an ordinary model carrying
160
- the frontier reasoning discipline inline, much cheaper; Fable 5 is the frontier model
161
- itself. Choose the default at install, switch it any time.
159
+ main session is ever touched. Primary Advisor uses Fable at medium effort for normal
160
+ consultation. Secondary Advisor uses Fable at high effort for explicit escalation.
161
+ Choose the default at install, and switch it any time.
162
162
 
163
163
  Plastic needs Ruby 3.0 or later and Node.js 18 or later. Most Linux systems already
164
164
  have a new enough Ruby. macOS ships Ruby 2.6, which is too old, so a clean Mac needs
@@ -5,6 +5,7 @@ description: |
5
5
  record, has the plan reviewed before code, dispatches one executor, reviews
6
6
  by risk, and closes.
7
7
  model: opus
8
+ effort: medium
8
9
  ---
9
10
 
10
11
  You are the Plastic Enforcer, the lead of an auto team. You ARE the orchestrating session,
@@ -14,8 +15,8 @@ spanning the whole What->Why->How->Exec cycle; you are not a separately dispatch
14
15
  orchestrating main session on the best available thinking model (Fable, Opus, or whatever
15
16
  supersedes them). This is advice only: it changes no behavior. Dispatched agents keep their
16
17
  configured model and never resolve to Fable unless an explicit `agents.models.<name>` config
17
- override names Fable for that role. The two advisors, `plastic-advisor` and
18
- `plastic-faux-advisor`, are consultation roles the user or the main session summons
18
+ override names Fable for that role. Primary Advisor and Secondary Advisor are consultation roles
19
+ the user or the main session summons
19
20
  deliberately; the auto pipeline never dispatches them.
20
21
 
21
22
  ## Your Responsibilities
@@ -4,6 +4,7 @@ description: |
4
4
  Use for the Exec stage in auto mode: commit the plan's tests red, implement
5
5
  the actions, check off the checklist, and drive the test suite green.
6
6
  model: sonnet
7
+ effort: medium
7
8
  ---
8
9
 
9
10
  You are the Plastic Executor. You own the Exec stage of the What->Why->How->Exec cycle.
@@ -11,6 +11,7 @@ tools:
11
11
  - Glob
12
12
  - Grep
13
13
  model: sonnet
14
+ effort: medium
14
15
  ---
15
16
 
16
17
  You are one node in Plastic's node-graph runner: one node input in, one YAML return out, no diff.
@@ -21,8 +22,9 @@ Code's `tools:` field grants or withholds a whole tool name, never a command pat
21
22
  is a write whatever command it runs. `Read`, `Glob`, and `Grep` are enough to read every file the
22
23
  node input points at.
23
24
 
24
- A research node produces no diff. Put what you found in your return's `findings`, never in a code
25
- change.
25
+ A research node produces no diff. Put short discoveries in `findings`. When the node input declares
26
+ `report:`, put the complete Markdown report in the return's `report` field. The runner writes that
27
+ declared intent resource after validation; you never edit it or any project file yourself.
26
28
 
27
29
  End your turn with exactly one YAML document, the return the dispatch line names, nothing else
28
30
  around it.
@@ -10,6 +10,7 @@ tools:
10
10
  - Glob
11
11
  - Grep
12
12
  model: opus
13
+ effort: medium
13
14
  ---
14
15
 
15
16
  You are one node in Plastic's node-graph runner: one node input in, one YAML return out, no diff.
@@ -13,6 +13,7 @@ tools:
13
13
  - Glob
14
14
  - Grep
15
15
  model: sonnet
16
+ effort: medium
16
17
  ---
17
18
 
18
19
  You are one node in Plastic's node-graph runner: one node input in, your files and your commit and
@@ -1,13 +1,12 @@
1
1
  ---
2
- name: plastic-advisor
2
+ name: plastic-primary-advisor
3
3
  description: |
4
- The real advisor: expensive, consultation-only, dispatched deliberately by
5
- the user or main session for the hardest reasoning, never by the auto
6
- pipeline. State an EFFORT line in the brief (low, medium, high, xhigh, or max) and
7
- the shape you need: a verdict plus the biggest risk for one bounded
4
+ The Primary Advisor: consultation-only, dispatched deliberately by
5
+ the user or main session for normal expensive reasoning, never by the auto
6
+ pipeline. State the shape you need: a verdict plus the biggest risk for one bounded
8
7
  decision; a stepped plan plus a risk map for a plan or plan review; rival
9
8
  approaches and kill criteria for architecture, one-way doors, or deadlocks. Model is set by config
10
- (agents.models.claude.plastic-advisor); fable is the shipped default.
9
+ (agents.models.claude.plastic-primary-advisor); fable is the shipped default.
11
10
  model: fable
12
11
  effort: medium
13
12
  ---
@@ -17,8 +16,7 @@ Protocol, whatever model is running you today. The caller pays premium rates
17
16
  for this consultation, so every sentence you return must earn its cost.
18
17
 
19
18
  **Your world is the brief.** The caller sends a natural-prose briefing that should
20
- cover: the goal and the decision the answer feeds, an EFFORT line (low, medium,
21
- high, xhigh, or max), the answer shape it needs, up to three questions, the
19
+ cover: the goal and the decision the answer feeds, the answer shape it needs, up to three questions, the
22
20
  caller's own candidate answer, evidence labeled verified/inferred/assumed, what
23
21
  was tried and how it failed, hard constraints, one-way doors, and the expected
24
22
  answer shape. Do not explore the repository or the web; if a load-bearing piece
@@ -1,16 +1,14 @@
1
1
  ---
2
- name: plastic-faux-advisor
2
+ name: plastic-secondary-advisor
3
3
  description: |
4
- The imitation advisor: an ordinary model carrying the frontier reasoning
5
- discipline below, so it reasons like the real advisor at a fraction of the
6
- cost. Consultation-only, dispatched deliberately by the user or main
7
- session, never by the auto pipeline. State an EFFORT line in the brief (low, medium, high, xhigh, or max) and
8
- the shape you need: a verdict plus the biggest risk for one bounded
4
+ The Secondary Advisor: an explicit escalation for difficult reasoning.
5
+ Consultation-only, dispatched deliberately by the user or main
6
+ session, never by the auto pipeline. State the shape you need: a verdict plus the biggest risk for one bounded
9
7
  decision; a stepped plan plus a risk map for a plan or plan review; rival
10
- approaches and kill criteria for architecture, one-way doors, or deadlocks. Model is set by config (agents.models.claude.plastic-faux-advisor);
11
- opus is the shipped default and the cheaper of the two advisors.
12
- model: opus
13
- effort: max
8
+ approaches and kill criteria for architecture, one-way doors, or deadlocks. Model is set by config (agents.models.claude.plastic-secondary-advisor);
9
+ fable is the shipped default.
10
+ model: fable
11
+ effort: high
14
12
  ---
15
13
 
16
14
  You are the advisor, consulted for expensive reasoning per the shipped Advisor
@@ -20,8 +18,7 @@ Operating Manual below is how you reach that bar: read it as your own working
20
18
  method, not as background reading.
21
19
 
22
20
  **Your world is the brief.** The caller sends a natural-prose briefing that should
23
- cover: the goal and the decision the answer feeds, an EFFORT line (low, medium,
24
- high, xhigh, or max), the answer shape it needs, up to three questions, the
21
+ cover: the goal and the decision the answer feeds, the answer shape it needs, up to three questions, the
25
22
  caller's own candidate answer, evidence labeled verified/inferred/assumed, what
26
23
  was tried and how it failed, hard constraints, one-way doors, and the expected
27
24
  answer shape. Do not explore the repository or the web; if a load-bearing piece
package/config_asks.yml CHANGED
@@ -32,7 +32,7 @@ config_asks:
32
32
  agents: ["claude"]
33
33
  question: "Which advisor should be the default?"
34
34
  options:
35
- - label: "Faux Fable (recommended): Opus 4.8 carrying the frontier reasoning instructions. Much cheaper, available on any plan, reasons in the same disciplined way."
36
- value: "plastic-faux-advisor"
37
- - label: "Fable 5: the frontier model itself. The strongest reasoning available, billed through usage credits, so summon it for a few rounds and close it."
38
- value: "plastic-advisor"
35
+ - label: "Primary Advisor (recommended): Fable at medium effort for normal consultation."
36
+ value: "plastic-primary-advisor"
37
+ - label: "Secondary Advisor: Fable at high effort for explicit escalation."
38
+ value: "plastic-secondary-advisor"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "2.0.0-alpha.26",
3
+ "version": "2.0.0-alpha.27",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/doctor.rb CHANGED
@@ -2159,6 +2159,8 @@ end
2159
2159
 
2160
2160
  global_config = load_yaml_safe(File.join(plastic_home, "config.yml")) || {}
2161
2161
  overrides = AgentModels.override_map(project_config: {}, global_config: global_config, harness: "codex")
2162
+ effort_overrides = AgentModels.effort_override_map(project_config: {}, global_config: global_config,
2163
+ harness: "codex")
2162
2164
 
2163
2165
  drifted = []
2164
2166
  sanctioned = []
@@ -2169,13 +2171,13 @@ end
2169
2171
  fields = codex_agent_toml_model_fields(File.read(path))
2170
2172
  override = overrides[basename]
2171
2173
 
2172
- if override
2174
+ if override || effort_overrides[basename]
2173
2175
  sanctioned << "#{basename}: toml model=#{fields[:model].inspect}, " \
2174
- "toml effort=#{fields[:effort].inspect}, sanctioned override=#{override.inspect}"
2175
- elsif AgentModels::TIER_DEFAULTS.key?(basename)
2176
- tier = AgentModels::TIER_DEFAULTS[basename]
2177
- expected_model = AgentModels.codex_model_for(tier)
2178
- expected_effort = AgentModels.effort_for(tier)
2176
+ "toml effort=#{fields[:effort].inspect}, sanctioned model override=#{override.inspect}, " \
2177
+ "effort override=#{effort_overrides[basename].inspect}"
2178
+ elsif AgentModels::SHIPPED_MODEL_DEFAULTS.key?(basename)
2179
+ expected_model = AgentModels.shipped_model_for(basename, harness: "codex")
2180
+ expected_effort = AgentModels.shipped_effort_for(basename)
2179
2181
  mismatches = []
2180
2182
  if expected_model && fields[:model] != expected_model
2181
2183
  mismatches << "model=#{fields[:model].inspect}, resolved default model=#{expected_model.inspect}"
@@ -2187,7 +2189,7 @@ end
2187
2189
  else
2188
2190
  unclassified << "#{basename}: toml model=#{fields[:model].inspect}, " \
2189
2191
  "toml effort=#{fields[:effort].inspect}, no resolved default (basename is in " \
2190
- "neither AgentModels::TIER_DEFAULTS nor AgentModels::CONSULTATION_AGENTS in " \
2192
+ "AgentModels::SHIPPED_MODEL_DEFAULTS in " \
2191
2193
  "scripts/lib/agent_models.rb; add it there, or set agents.models.codex.#{basename} " \
2192
2194
  "to sanction a model explicitly)"
2193
2195
  end
@@ -81,6 +81,7 @@ class Install < InstallerCore
81
81
 
82
82
  distribute(mode)
83
83
  bootstrap if fresh
84
+ migrate_advisor_config_file(File.join(plastic_home, "config.yml"))
84
85
  apply_config_flags(argv)
85
86
 
86
87
  results = selected.map do |key|
@@ -239,10 +240,9 @@ class Install < InstallerCore
239
240
  --no-advisor Skip installing both advisor agents and the agent-advisor
240
241
  skill (advisor.enabled: false)
241
242
  --advisor VALUE Which advisor agent is the default: an agent name, or the
242
- shorthand "real" (plastic-advisor) or "faux"
243
- (plastic-faux-advisor). Writes advisor.claude.default. Left
244
- unset, the agent-advisor skill falls back to
245
- plastic-faux-advisor at consult time.
243
+ shorthand "primary" or "secondary". Legacy "real" and
244
+ "faux" aliases remain accepted. Writes advisor.claude.default.
245
+ Left unset, the agent-advisor skill falls back to Primary Advisor.
246
246
  -h, --help Show this help
247
247
 
248
248
  Notes:
@@ -83,7 +83,7 @@ module ActionGraphShim
83
83
  end
84
84
 
85
85
  # Node records for either shape (D8), always carrying NodeFile.parse's own
86
- # key set (ok:, node:, kind:, files:, budget:, body:, errors:) plus
86
+ # key set (ok:, node:, kind:, files:, budget:, report:, body:, errors:) plus
87
87
  # needs:, path: and proven_by:. [] on :none, never a raise on a real
88
88
  # directory.
89
89
  def nodes(intent_dir)
@@ -153,6 +153,7 @@ module ActionGraphShim
153
153
  kind: "work",
154
154
  files: files_section_paths(text),
155
155
  budget: nil,
156
+ report: nil,
156
157
  body: text,
157
158
  errors: [],
158
159
  needs: needs_targets,
@@ -166,6 +167,7 @@ module ActionGraphShim
166
167
  kind: "work",
167
168
  files: [],
168
169
  budget: nil,
170
+ report: nil,
169
171
  body: nil,
170
172
  errors: ["could not read #{path}: #{e.message}"],
171
173
  needs: needs_targets,
@@ -14,8 +14,7 @@ module AgentModels
14
14
  # HarnessAdapter::AGENT_TYPE_BY_KIND. work and research resolve the
15
15
  # executor tier, verify the advisor tier, mirroring RunnerPolicy's
16
16
  # model_role split (327 D12): work and research run on plastic-executor's
17
- # own tier, verify on the same tier plastic-advisor's imitation
18
- # (plastic-faux-advisor) ships, never the cheap tier.
17
+ # own tier, and verify on the lifecycle advisor tier, never the cheap tier.
19
18
  TIER_DEFAULTS = {
20
19
  "plastic-enforcer" => "opus",
21
20
  "plastic-executor" => "sonnet",
@@ -24,32 +23,35 @@ module AgentModels
24
23
  "plastic-node-research" => "sonnet"
25
24
  }.freeze
26
25
 
27
- # The two advisor agents (intent 185 final design): plastic-advisor (the real
28
- # advisor, ships `model: fable`) and plastic-faux-advisor (the imitation
29
- # advisor, an ordinary model carrying the same reasoning discipline inline,
30
- # ships `model: opus`). Both are shipped DEFAULTS in frontmatter, never a
26
+ # The two consultation agents are Primary Advisor and Secondary Advisor.
27
+ # Both ship Fable; Primary uses medium effort and Secondary uses high effort.
28
+ # Both are shipped defaults in frontmatter, never a
31
29
  # hard-wired identity: agents.models.claude.<name> (or the legacy flat form)
32
30
  # overrides either one through the same install-time frontmatter rewrite
33
31
  # every agent override uses. Neither is a lifecycle-stage role: never
34
- # dispatched by the auto pipeline, not part of TIER_DEFAULTS. Claude-only for
35
- # this release (generate_codex_agents skips both by name; the Codex advisor
36
- # case is intent 186, not a permanent exclusion).
37
- #
38
- # Intent 186 DEFINES the advisor Codex pairing but keeps emission deferred: when the skip is
39
- # lifted, plastic-advisor pairs to gpt-5.6-sol at xhigh (the deepest) and plastic-faux-advisor
40
- # to gpt-5.6-terra at high (cheaper). Neither is in TIER_DEFAULTS and neither is auto-dispatched.
41
- CONSULTATION_AGENTS = %w[plastic-advisor plastic-faux-advisor].freeze
42
-
43
- # Codex reasoning-effort per tier alias (intent 102a). model_reasoning_effort is a
44
- # depth-of-thinking dial independent of model selection (181 line 317-318), so mapping
45
- # the tier here never encodes a rotting Codex model id (116 D1). opus is the deepest
46
- # reasoning tier -> the deepest generally-safe effort (high, not the model-dependent
47
- # xhigh); sonnet the mid execution tier -> medium; haiku the lightest -> low. minimal is
48
- # unused.
32
+ # dispatched by the auto pipeline, not part of TIER_DEFAULTS. Codex installs
33
+ # generate both advisors with Astra and preserve each role's effort.
34
+ CONSULTATION_AGENTS = %w[plastic-primary-advisor plastic-secondary-advisor].freeze
35
+
36
+ SHIPPED_MODEL_DEFAULTS = TIER_DEFAULTS.merge(
37
+ "plastic-primary-advisor" => "fable",
38
+ "plastic-secondary-advisor" => "fable"
39
+ ).freeze
40
+
41
+ # Codex reasoning effort per tier alias. Model choice and reasoning effort are independent:
42
+ # aliases select the recommended OpenAI model, while every Plastic role starts at medium.
43
+ # A valid harness-scoped user override can still choose another effort for one agent.
49
44
  EFFORT_BY_ALIAS = {
50
- "opus" => "high",
45
+ "opus" => "medium",
51
46
  "sonnet" => "medium",
52
- "haiku" => "low"
47
+ "haiku" => "medium"
48
+ }.freeze
49
+
50
+ DEFAULT_EFFORT = "medium"
51
+
52
+ SHIPPED_EFFORT_DEFAULTS = {
53
+ "plastic-primary-advisor" => "medium",
54
+ "plastic-secondary-advisor" => "high"
53
55
  }.freeze
54
56
 
55
57
  # Codex model id per tier alias (intent 186). Codex has NO vendor alias layer: every model id
@@ -58,17 +60,26 @@ module AgentModels
58
60
  # centralizing every id in ONE map: Plastic owns the alias, so per-role identity costs a single
59
61
  # line to refresh on a Codex deprecation plus a Plastic release, and no per-role file carries a
60
62
  # raw id. opus (deepest reasoning tier) -> the flagship Sol; sonnet (execution tier) -> the
61
- # balanced Terra; haiku (lightest) -> the fast/cheap Luna. Paired with EFFORT_BY_ALIAS so
62
- # reasoning roles get a stronger model AND higher effort than executors. This is a shipped
63
- # DEFAULT, fully overridable via agents.models.codex.<name>.
63
+ # balanced Terra; haiku (lightest) -> the fast/cheap Luna. This is a shipped
64
+ # DEFAULT, fully overridable via agents.models.codex.<name>. Model strength does not change
65
+ # effort: all three aliases use medium unless agents.efforts.codex.<name> overrides it.
64
66
  CODEX_MODEL_BY_ALIAS = {
65
67
  "opus" => "gpt-5.6-sol",
66
68
  "sonnet" => "gpt-5.6-terra",
67
69
  "haiku" => "gpt-5.6-luna"
68
70
  }.freeze
69
71
 
72
+ CODEX_MODEL_BY_AGENT = {
73
+ "plastic-primary-advisor" => "gpt-6-astra",
74
+ "plastic-secondary-advisor" => "gpt-6-astra"
75
+ }.freeze
76
+
70
77
  module_function
71
78
 
79
+ def shipped_effort_for(agent)
80
+ SHIPPED_EFFORT_DEFAULTS.fetch(agent.to_s, DEFAULT_EFFORT)
81
+ end
82
+
72
83
  # Pull { basename => model } out of a loaded config hash's `agents.models`
73
84
  # section, scoped to `harness` ("claude" or "codex"), tolerating a missing or
74
85
  # malformed shape. `agents.models` can mix two shapes: legacy FLAT scalar
@@ -124,4 +135,11 @@ module AgentModels
124
135
  def codex_model_for(value)
125
136
  CODEX_MODEL_BY_ALIAS[value.to_s]
126
137
  end
138
+
139
+ def shipped_model_for(agent, harness: "claude")
140
+ value = SHIPPED_MODEL_DEFAULTS[agent.to_s]
141
+ return value unless harness.to_s == "codex"
142
+
143
+ CODEX_MODEL_BY_AGENT[agent.to_s] || codex_model_for(value) || value
144
+ end
127
145
  end
@@ -70,15 +70,16 @@ module CodexAdapter
70
70
  # worktree, never `~/.plastic`), `--output-last-message` at the path the
71
71
  # caller names, and a bare `-` so the prompt is read from stdin (row 6.9) -
72
72
  # the node input itself never rides in this array.
73
- def build_argv(kind:, worktree:, output_last_message:)
74
- [
73
+ def build_argv(kind:, worktree:, output_last_message:, model: nil, effort: nil)
74
+ argv = [
75
75
  "codex", "exec",
76
76
  "-C", worktree.to_s,
77
77
  "--sandbox", sandbox_mode(kind),
78
78
  *add_dir_args(kind: kind, worktree: worktree),
79
- "--output-last-message", output_last_message.to_s,
80
- "-",
81
79
  ]
80
+ argv += ["--model", model.to_s] unless model.to_s.strip.empty?
81
+ argv += ["--config", %(model_reasoning_effort="#{effort}")] unless effort.to_s.strip.empty?
82
+ argv + ["--output-last-message", output_last_message.to_s, "-"]
82
83
  end
83
84
 
84
85
  # matrix row 6.23: the wall-clock bound comes from the KIND's own lease
@@ -24,7 +24,7 @@ require_relative "agent_models"
24
24
  # through `RunnerPolicy.model_for(kind, config:)`, the one place that maps a
25
25
  # node's kind to the role the runner actually dispatched it against - never
26
26
  # a hand-rolled kind-to-role table, and never `AgentModels::TIER_DEFAULTS`
27
- # directly, which carries no `plastic-advisor` entry at all (that agent is a
27
+ # directly, which carries no `plastic-primary-advisor` entry at all (that agent is a
28
28
  # consultation agent, never auto-dispatched). Adds no second parser:
29
29
  # `NodeLedger` owns the transition-line format, `NodeFile` owns the kind
30
30
  # envelope and the `KIND_PREFIX` fallback, `RunnerPolicy`/`AgentModels` own