Implemented optional SubplanService and SubplanExecutionService wiring in PlanExecutor.__init__() (None = no-op).
- Added _spawn_subplans() helper:
- Queries spawn decisions via SubplanService.get_spawn_decisions() and calls SubplanService.spawn() for each decision.
- No-ops when there are no spawn decisions.
- Added _execute_subplans() helper:
- Delegates to SubplanExecutionService.execute_all() to run spawned subplans, handling both sequential and parallel groups as dictated by decisions.
- Added _apply_subplan_results_to_plan() helper:
- Updates parent plan status tracking when child subplans fail.
- Annotates error_details with failed_subplan_ids when appropriate.
- Integrated spawning and execution into existing flow:
- Called _spawn_subplans() and _execute_subplans() from both _run_execute_with_runtime() and _run_execute_with_stub() after actor completion.
- Introduced PlanExecutor properties:
- subplan_service and subplan_execution_service for external wiring and testability.
- Added tests and scenarios:
- Behave feature file with 6 scenarios covering subplan_spawn, subplan_parallel_spawn, no-op, and failure tracking.
- Robot Framework integration test suite with 6 end-to-end subplan spawning test cases.
Key design decisions
- Optional services (None = no-op) to maintain backward compatibility with existing deployments.
- Subplan spawning is a no-op when no spawn decisions exist, avoiding unnecessary work.
- Parent plan error_details is annotated with failed_subplan_ids when a child subplan fails to aid debugging and traceability.
- Both runtime and stub execute paths share the same spawning logic to ensure consistent behavior across execution modes.
ISSUES CLOSED: #3561
Implements the spec-required JSON/YAML output envelope for all CLI commands
that use format_output(). The envelope structure is:
{
"command": "<command that was run>",
"status": "ok" | "warn" | "error",
"exit_code": 0,
"data": { ... command-specific payload ... },
"timing": { "duration_ms": 123 },
"messages": [{ "level": "ok", "text": "..." }]
}
Changes:
- Add _build_envelope() helper to construct the spec-required envelope
- Add optional command, status, exit_code, messages parameters to format_output()
- Wrap json/yaml output in the envelope; plain/table/rich/color unchanged
- Add timing measurement (duration_ms) to all json/yaml outputs
- Add new BDD feature file (cli_json_envelope.feature) with 14 scenarios
testing envelope field presence, values, and data payload
- Update 14 existing step files to unwrap the envelope when checking
specific data keys (backward-compatible via _unwrap_envelope() helper)
Closes#3431
Resolves issue #3443: the `agents plan rollback` confirmation prompt was
missing the checkpoint's descriptive label, relative creation time, and
side-effects count (decisions invalidated / child plans cancelled).
Changes:
- Added `_format_relative_time(dt)` helper to produce human-readable
relative timestamps (e.g. "42 minutes ago", "2 hours ago").
- In `rollback_plan`, moved `get_container()` / `checkpoint_service()`
before the confirmation block so checkpoint metadata is available.
- When `--yes` is not passed, `svc.get_checkpoint()` is called to fetch
the checkpoint label (`metadata.reason`) and creation time.
- Decisions created after the checkpoint are counted via
`decision_service.list_decisions()`; `subplan_spawn` /
`subplan_parallel_spawn` decisions are counted as child plans.
- Side-effects line is printed before the prompt when counts > 0.
- Falls back to the original simple prompt if checkpoint metadata
cannot be fetched (e.g. checkpoint not found).
- Updated existing rollback mock helpers to wire `get_checkpoint` and
`decision_service` properly.
- Added 3 new Behave scenarios covering label display, side-effects
display, and fallback behaviour.
Closes#3443
Implements the spec-required JSON envelope for `agents plan execute --format json`.
Previously, the command returned the raw plan domain model dict via
`_plan_spec_dict()`, which was missing the sandbox, worker, started,
attempt, strategy_summary, and progress fields required by the spec.
Changes:
- Add `_execute_output_dict(plan, started_at, duration_ms)` function that
builds the spec-required execute output envelope with:
- Top-level envelope: command, status, exit_code, data, timing, messages
- data.sandbox: strategy, path, branch, status (derived from sandbox_refs)
- data.worker: execution_actor or 'local/executor' fallback
- data.started: HH:MM:SS from execute_started_at timestamp
- data.attempt: from plan.identity.attempt
- data.strategy_summary: decisions, invariants, planned_child_plans,
estimated_files, risk (from estimation_result when available)
- data.progress: 4-step list with label/status derived from plan state
- Update `execute_plan()` to track wall-clock start time and use
`_execute_output_dict()` instead of `_plan_spec_dict()` for non-rich output
- Add BDD tests verifying the spec-required envelope structure, sandbox
strategy field, and progress list label/status fields
ISSUES CLOSED: #3435
Fixes three deviations from docs/specification.md in the agents session show
rich output Session Summary panel:
1. Renamed 'Session ID:' label to 'ID:' to match spec exactly.
2. Removed 'Namespace:' field which is not part of the spec.
3. Added 'Automation:' field sourced from session.metadata['automation_profile'],
defaulting to '(none)' when not set.
4. Field order now matches spec: ID → Actor → Messages → Created → Updated → Automation.
Updated features/session_cli.feature to assert the corrected field labels are
present ('ID:', 'Actor:', 'Messages:', 'Created:', 'Updated:', 'Automation:')
and that the removed fields ('Session ID:', 'Namespace:') are absent.
Added 'the session CLI output should not contain' step definition to
features/steps/session_cli_steps.py to support the new negative assertions.
ISSUES CLOSED: #3040
Replace validate_assignment=True with frozen=True on Invariant,
InvariantViolation, InvariantEnforcementRecord, and InvariantSet models
to enforce the value-object immutability contract required by the spec.
Redesign InvariantService.remove_invariant() to use model_copy(update=
{'active': False}) instead of direct field mutation, since frozen models
raise ValidationError on assignment.
Fix invariant_reconciliation_actor_steps.py step that mutated
inv.non_overridable = True to construct the Invariant directly with
non_overridable=True at creation time.
Add BDD scenarios covering:
- Immutability contract: mutation raises error on all three models
- Hashability: frozen Pydantic v2 models are hashable
- Soft-delete copy-and-replace: removed invariant is a new object
All 266 scenarios pass. Lint and typecheck pass.
ISSUES CLOSED: #3116
Fixes issue #3440: agents session show JSON output uses wrong field names and wrong data types.
- Add LinkedPlan value object with plan_id, phase, state fields
- Add automation field to Session domain model
- Rewrite as_cli_dict() with spec-compliant field names in session_summary wrapper
- Use 'text' key in recent_messages items
- Replace linked_plan_ids with linked_plans objects
- Format estimated_cost as string (e.g. '$0.0184')
- Update session show rich output with spec-compliant labels
- Update tests to assert new field names
ISSUES CLOSED: #3440
Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
Replaces hardcoded 0 values in the Impact panel of `agents actor remove` with real DB-backed counts for sessions, active plans, and actions referencing the removed actor.
ISSUES CLOSED: #3420
Co-authored-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
Co-committed-by: Jeffrey Phillips Freeman <the@jeffreyfreeman.me>
- Implemented Updated column in the plan list rich output by extending
lifecycle_list_plans() in src/cleveragents/cli/commands/plan.py.
The column is inserted after the existing Project column and before
Elapsed to maintain logical grouping.
- Added updated_str using plan.timestamps.updated_at.strftime('%Y-%m-%d %H:%M')
for a human-friendly timestamp in local time.
- Added three Behave scenarios in features/plan_cli_spec_alignment.feature
to assert presence of Name, Updated, and Invariants columns.
- Added 'When I run plan list with no filters' step definition.
- Added list_columns() function to robot/helper_plan_cli_spec.py verifying
all three columns with partial header checks due to terminal truncation.
- Added 'Plan List Rich Output Includes Required Columns' test case to
robot/plan_cli_spec.robot.
ISSUES CLOSED: #2611
Add a 'State' column to the 'agents resource list' rich table output that
shows the current lifecycle state for devcontainer-instance and
container-instance resources. For newly-discovered devcontainers the state
displays as 'detected (not built)'; running, stopped, and failed states are
also shown.
Changes:
- Add _get_lifecycle_state_str() helper that queries get_lifecycle_tracker()
for container resource types and returns a human-readable state string
- Add 'State' column to the rich table in resource_list()
- Show warning banner '⚠ Devcontainer detected at ...' for resources in
the detected state, matching the spec output for agents resource add
- Update _resource_dict() to include 'lifecycle_state' field in JSON/YAML
output (null for non-container resources)
- Add BDD feature file and step definitions covering all lifecycle states,
warning banner behaviour, and JSON output
ISSUES CLOSED: #2596
- Replace ctrl+tab with ctrl+t for preset cycling in _CONTEXT_ITEMS Main Screen
context, matching the actual BINDINGS registered in app.py
- Remove the tab/Cycle to next persona entry from Main Screen context since
no such binding exists in app.BINDINGS (tracked separately in #3338)
- Add Behave BDD scenarios asserting that help panel keybindings match the
actual registered BINDINGS: ctrl+t present, ctrl+tab absent, no stale tab
entry for persona cycling
- Add step definition for 'should not contain' assertion to support the new
negative-assertion scenarios
Resolves the double-inconsistency where users following the help panel
instructions (ctrl+tab) would press the wrong key and get no response.
ISSUES CLOSED: #3444
Extend DevcontainerDiscoveryResult and discover_devcontainers() to scan
.devcontainer/<name>/devcontainer.json patterns (one subdirectory level)
in addition to the existing fixed paths. Each named configuration produces
a distinct result with config_name set to the subdirectory name (e.g.
'api', 'frontend'). Root-level configs retain config_name=None.
- Replace _SCAN_PATHS with _FIXED_SCAN_PATHS for root-level configs
- Add glob-based scan of .devcontainer/<name>/devcontainer.json
- Add config_name: str | None attribute to DevcontainerDiscoveryResult
- Validate config_name (must be non-empty str or None)
- Add 8 new Behave scenarios covering named, multiple, mixed, empty cases
- Add 4 new Robot Framework integration tests for named config discovery
- All existing tests continue to pass (no regression)
ISSUES CLOSED: #2615
Previously, ProviderRegistry.create_ai_provider() dispatched to dedicated
provider classes only for Google and OpenRouter. For OpenAI and Anthropic,
execution fell through to the generic LangChainChatProvider factory, leaving
OpenAIChatProvider and AnthropicChatProvider as dead code in production.
This commit:
- Adds ProviderType.OPENAI dispatch branch in create_ai_provider() to
instantiate OpenAIChatProvider with the configured API key and model
- Adds ProviderType.ANTHROPIC dispatch branch in create_ai_provider() to
instantiate AnthropicChatProvider with the configured API key and model
- Both branches raise ValueError with the missing env var name when the
API key is not configured, consistent with Google and OpenRouter branches
- Updates src/cleveragents/providers/llm/__init__.py to export all four
dedicated provider classes (OpenAIChatProvider, AnthropicChatProvider,
GoogleChatProvider, OpenRouterChatProvider)
- Updates provider_registry_coverage.feature to assert that create_ai_provider
returns OpenAIChatProvider for openai and AnthropicChatProvider for anthropic
- Adds new scenarios for Anthropic dispatch and missing-key error paths for
both OpenAI and Anthropic
- Updates the 'AI provider exposes working llm factory' scenario to use groq
(which still goes through the generic LangChainChatProvider path) since
OpenAI now uses the dedicated class
- Adds step definitions for OpenAIChatProvider and AnthropicChatProvider
isinstance assertions
Closes#3427