generated from coulomb/repo-seed
Compare commits
75 Commits
aea790622a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4f5df8ee5b | |||
| 6018e42930 | |||
| f15d253e64 | |||
| a6266f7e2c | |||
| 8e39e8e27c | |||
| a0f79af2ab | |||
| 79736d8b08 | |||
| d95dbb8ef5 | |||
| 019f6e7dc7 | |||
| cd5db14fbf | |||
| 34528308bb | |||
| f0ee0636c0 | |||
| c14d09c14d | |||
| a0d24a31eb | |||
| 099ba99a6a | |||
| 04ec282a1d | |||
| b68164b237 | |||
| f4e965cc04 | |||
| ad11632bd4 | |||
| 88ec1381e3 | |||
| c60b3b5001 | |||
| 0a85539d8d | |||
| 6d93f6dd90 | |||
| a87cd4ab42 | |||
| 16fde868cf | |||
| cd3a2fbecc | |||
| 19e80cc9bc | |||
| 2bcbe50607 | |||
| c1aee5087e | |||
| 02d6f3447b | |||
| c7865929dc | |||
| 0b25758b77 | |||
| 996039bbe1 | |||
| 31086a064c | |||
| c0039c293a | |||
| 69de28b1fe | |||
| 358907b51f | |||
| 2306f05ebf | |||
| e6b9610b25 | |||
| f500a35ff0 | |||
| b483bf1f34 | |||
| 8c7bfb57f4 | |||
| 2f1fba9767 | |||
| 379c68097f | |||
| 62b046b2f8 | |||
| f9feef6a7d | |||
| 4af53b2cbd | |||
| 3c11afafcf | |||
| 2daba29555 | |||
| aad9346c9d | |||
| 4a48e6ad07 | |||
| 449361fe8b | |||
| b2ec65160b | |||
| c4b3b3908f | |||
| d2beaaa5af | |||
| 9865245d6b | |||
| 6cbd055b1a | |||
| f2c555284f | |||
| 7bcf28d720 | |||
| d904e1bdb4 | |||
| abc54989e5 | |||
| 03aaaf99e5 | |||
| cdae2fac2f | |||
| 9e249d859b | |||
| 3bc35a2b30 | |||
| 0b9e64164a | |||
| cda25f0967 | |||
| 174f876626 | |||
| 734dc6f608 | |||
| b7a2cc48ba | |||
| 98a43f5671 | |||
| 66c7ed3806 | |||
| e8f7320bb4 | |||
| 905485acce | |||
| 1bce4bd7bc |
20
.claude/rules/agents.md
Normal file
20
.claude/rules/agents.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## Kaizen Agents
|
||||
|
||||
Specialized agent personas available on demand via the state-hub MCP.
|
||||
|
||||
**Discover:** `list_kaizen_agents()` — returns all agents with name, description, category
|
||||
**Load:** `get_kaizen_agent("tdd-workflow")` — returns full instructions; read and follow them
|
||||
|
||||
Common agents:
|
||||
|
||||
| Agent | Category | When to use |
|
||||
|-------|----------|-------------|
|
||||
| `tdd-workflow` | testing | Step-by-step TDD8 workflow for any feature |
|
||||
| `code-refactoring` | quality | Code quality analysis and safe refactoring |
|
||||
| `test-maintenance` | testing | Diagnose and fix failing tests |
|
||||
| `requirements-engineering` | process | Prevent interface/mock mismatches upfront |
|
||||
| `keepaTodofile` | process | Maintain TODO.md during work |
|
||||
| `project-management` | process | Track status, determine next steps |
|
||||
| `datamodel-optimization` | quality | Optimize dataclasses and data structures |
|
||||
|
||||
All 17 agents: call `list_kaizen_agents()` for the full list.
|
||||
8
.claude/rules/architecture.md
Normal file
8
.claude/rules/architecture.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## Architecture
|
||||
|
||||
<!-- TODO: Describe the key design decisions and component structure.
|
||||
Key modules, data flows, external integrations, state machines, etc. -->
|
||||
|
||||
## Quick Reference
|
||||
|
||||
`~/state-hub/mcp_server/TOOLS.md` — MCP tool reference
|
||||
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Credential and access routing
|
||||
|
||||
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||
|
||||
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||
other credential need belongs to another subsystem. **Do not** message
|
||||
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||
|
||||
### Lookup (do this first)
|
||||
|
||||
```bash
|
||||
warden route find "<describe your need>" --json
|
||||
warden route show <catalog-id> --json
|
||||
```
|
||||
|
||||
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||
|
||||
| Agent runtime | How to orient |
|
||||
| --- | --- |
|
||||
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=can-you-assist` is for coordination, not secret vending |
|
||||
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||
|
||||
### Quick routing table
|
||||
|
||||
| I need… | Owner | ops-warden executes? |
|
||||
| --- | --- | --- |
|
||||
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||
| Authorization decision | flex-auth | No — route only |
|
||||
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||
|
||||
### Anti-patterns (do not do these)
|
||||
|
||||
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||
|
||||
### Other capabilities (reuse-surface)
|
||||
|
||||
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||
get wrong.
|
||||
|
||||
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||
38
.claude/rules/first-session.md
Normal file
38
.claude/rules/first-session.md
Normal file
@@ -0,0 +1,38 @@
|
||||
## First Session Protocol
|
||||
|
||||
Triggered when `get_domain_summary("agents")` shows **no workstreams**.
|
||||
The project is registered but work has not yet been structured.
|
||||
|
||||
**Step 1 — Read, don't write**
|
||||
- `~/the-custodian/canon/projects/agents/project_charter_v0.1.md` — purpose, scope
|
||||
- `~/the-custodian/canon/projects/agents/roadmap_v0.1.md` — planned phases
|
||||
- Scan repo root: README, directory structure, existing code or docs
|
||||
|
||||
**Step 2 — Survey in-progress work**
|
||||
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
|
||||
|
||||
**Step 3 — Propose workstreams to Bernd**
|
||||
Propose 1–3 workstreams — each a coherent strand, weeks to months, anchored to a
|
||||
roadmap phase. **Wait for approval before creating.**
|
||||
|
||||
**Step 4 — Create workplan file first, then DB record (ADR-001)**
|
||||
```
|
||||
workplans/CYA-WP-NNNN-<slug>.md ← write this first
|
||||
```
|
||||
Then register in the hub:
|
||||
```
|
||||
create_workstream(topic_id="64418556-3206-457a-ba29-6884b5b12cf3", title="...", owner="...", description="...")
|
||||
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
|
||||
```
|
||||
|
||||
**Step 5 — Record the setup**
|
||||
```
|
||||
add_progress_event(
|
||||
summary="First session: structured agents into N workstreams, M tasks",
|
||||
event_type="milestone",
|
||||
topic_id="64418556-3206-457a-ba29-6884b5b12cf3",
|
||||
detail={"workstreams": [...], "tasks_created": M}
|
||||
)
|
||||
```
|
||||
|
||||
<!-- Delete or archive this file once past first session -->
|
||||
8
.claude/rules/repo-boundary.md
Normal file
8
.claude/rules/repo-boundary.md
Normal file
@@ -0,0 +1,8 @@
|
||||
## Repo boundary
|
||||
|
||||
This repo owns **can-you-assist** only. It does not own:
|
||||
|
||||
<!-- TODO: List what belongs in adjacent repos, e.g.:
|
||||
- SSH key management → railiance-infra/
|
||||
- State hub code → state-hub/
|
||||
-->
|
||||
5
.claude/rules/repo-identity.md
Normal file
5
.claude/rules/repo-identity.md
Normal file
@@ -0,0 +1,5 @@
|
||||
**Purpose:** Console-native, backend-agnostic LLM helper for practical local shell, repository, filesystem, and note workflows.
|
||||
|
||||
**Domain:** agents
|
||||
**Repo slug:** can-you-assist
|
||||
**Topic ID:** 64418556-3206-457a-ba29-6884b5b12cf3
|
||||
85
.claude/rules/session-protocol.md
Normal file
85
.claude/rules/session-protocol.md
Normal file
@@ -0,0 +1,85 @@
|
||||
## Session Protocol
|
||||
|
||||
Dev Hub (State Hub API): http://127.0.0.1:8000
|
||||
MCP server name in `~/.claude.json`: `dev-hub`
|
||||
|
||||
**Step 1 — Orient**
|
||||
|
||||
Read the offline-safe brief first — it works without a live hub connection:
|
||||
```bash
|
||||
cat .custodian-brief.md
|
||||
```
|
||||
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
|
||||
```
|
||||
get_domain_summary("agents")
|
||||
```
|
||||
If MCP tools are unavailable in the current agent session, use the REST API:
|
||||
```bash
|
||||
curl -s "http://127.0.0.1:8000/state/summary" | python3 -m json.tool
|
||||
```
|
||||
If the hub is offline: `cd ~/state-hub && make api`
|
||||
|
||||
**Step 2 — Check inbox**
|
||||
With MCP tools:
|
||||
```
|
||||
get_messages(to_agent="can-you-assist", unread_only=True)
|
||||
```
|
||||
Mark read with `mark_message_read(message_id)`. Reply or act on coordination
|
||||
requests before proceeding.
|
||||
|
||||
Without MCP tools:
|
||||
```bash
|
||||
curl -s "http://127.0.0.1:8000/messages/?to_agent=can-you-assist&unread_only=true" \
|
||||
| python3 -m json.tool
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||
-H "Content-Type: application/json" -d '{}'
|
||||
```
|
||||
|
||||
**Step 3 — Scan workplans**
|
||||
```bash
|
||||
ls workplans/
|
||||
```
|
||||
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||
`wait`/`todo`/`progress` tasks.
|
||||
|
||||
**Step 4 — Present brief**
|
||||
|
||||
1. **Active workstreams** for `agents` — title, task counts, blocking decisions
|
||||
2. **Pending tasks** from `workplans/` + any `[repo:can-you-assist]` hub tasks
|
||||
3. **Goal guidance** — if `goal_guidance` in summary:
|
||||
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
||||
- `alignment_warnings`: flag if active work is not aligned with current goal
|
||||
4. **Suggested next action** — highest-priority open item
|
||||
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
|
||||
|
||||
If no workstreams: follow First Session Protocol (`first-session.md`).
|
||||
|
||||
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
|
||||
|
||||
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
|
||||
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
|
||||
|
||||
**Session close:**
|
||||
With MCP tools:
|
||||
```
|
||||
add_progress_event(summary="...", topic_id="64418556-3206-457a-ba29-6884b5b12cf3", workstream_id="<uuid>")
|
||||
```
|
||||
Without MCP tools:
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"topic_id":"64418556-3206-457a-ba29-6884b5b12cf3","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
|
||||
```
|
||||
If workplan files were modified, ensure the local copy is up to date first:
|
||||
```bash
|
||||
git -C <repo_path> pull --ff-only
|
||||
cd ~/state-hub && make fix-consistency REPO=can-you-assist
|
||||
```
|
||||
For repos where implementation runs on a remote machine (e.g. CoulombCore),
|
||||
use the combined target which pulls before fixing:
|
||||
```bash
|
||||
cd ~/state-hub && make fix-consistency-remote REPO=can-you-assist
|
||||
```
|
||||
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
|
||||
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes
|
||||
until you pull — intentional to prevent clobbering remote progress.
|
||||
19
.claude/rules/stack-and-commands.md
Normal file
19
.claude/rules/stack-and-commands.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## Stack
|
||||
|
||||
<!-- TODO: Fill in language, frameworks, and key dependencies -->
|
||||
- **Language:**
|
||||
- **Key deps:**
|
||||
|
||||
## Dev Commands
|
||||
|
||||
```bash
|
||||
# TODO: Fill in the standard commands for this repo
|
||||
|
||||
# Install dependencies
|
||||
|
||||
# Run tests
|
||||
|
||||
# Lint / type check
|
||||
|
||||
# Build / package (if applicable)
|
||||
```
|
||||
40
.claude/rules/workplan-convention.md
Normal file
40
.claude/rules/workplan-convention.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## Workplan Convention (ADR-001)
|
||||
|
||||
File location: `workplans/CYA-WP-NNNN-<slug>.md`
|
||||
ID prefix: `CYA-WP-`
|
||||
|
||||
Work items originate as files in this repo **before** being registered in the hub.
|
||||
|
||||
Canonical workplan/workstream frontmatter statuses are:
|
||||
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
|
||||
Use `proposed` for a newly drafted plan, `ready` after review against current
|
||||
repo state, and `finished` when implementation is complete. `stalled` and
|
||||
`needs_review` are derived health labels, not stored statuses.
|
||||
|
||||
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
||||
prefix: `YYMMDD-CYA-WP-NNNN-<slug>.md`. The frontmatter id remains
|
||||
unchanged; the prefix is only for quick visual reference.
|
||||
|
||||
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
||||
`workplans/ADHOC-YYYY-MM-DD.md`, workstream slug `adhoc-YYYY-MM-DD`, and task ids
|
||||
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
||||
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
||||
multiple planned phases into a normal workplan.
|
||||
|
||||
Ecosystem todos from other agents arrive as `[repo:can-you-assist]` hub tasks —
|
||||
visible at session start. Pick one up by creating the workplan file, then registering
|
||||
the workstream.
|
||||
|
||||
Task blocks use this shape:
|
||||
|
||||
```task
|
||||
id: CYA-WP-NNNN-T01
|
||||
status: wait | todo | progress | done | cancel
|
||||
priority: high | medium | low
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
```
|
||||
|
||||
Status progression is `todo` → `progress` → `done`; use `wait` for waiting or
|
||||
blocked work and `cancel` for stopped work.
|
||||
|
||||
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
||||
@@ -1,8 +1,8 @@
|
||||
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
|
||||
# Custodian Brief — can-you-assist
|
||||
|
||||
**Domain:** capabilities
|
||||
**Last synced:** 2026-05-26 00:27 UTC
|
||||
**Domain:** agents
|
||||
**Last synced:** 2026-06-23 12:26 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Current Goal
|
||||
@@ -17,6 +17,6 @@ Console-native user-controlled LLM assistant
|
||||
## MCP Orientation (when available)
|
||||
|
||||
If the state-hub MCP server is reachable, call:
|
||||
`get_domain_summary("capabilities")`
|
||||
`get_domain_summary("agents")`
|
||||
This provides richer cross-domain context.
|
||||
If the MCP call fails, use this file as your orientation source.
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -99,7 +99,7 @@ ipython_config.py
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
@@ -171,6 +171,10 @@ cython_debug/
|
||||
# Ruff stuff:
|
||||
.ruff_cache/
|
||||
|
||||
# Generated by setuptools_scm
|
||||
src/cya/_version.py
|
||||
|
||||
# PyPI configuration file
|
||||
.pypirc
|
||||
|
||||
.claude/ralph-loop.local.md
|
||||
|
||||
16
.repo-classification.yaml
Normal file
16
.repo-classification.yaml
Normal file
@@ -0,0 +1,16 @@
|
||||
repo_classification:
|
||||
standard: Repo Classification Standard
|
||||
version: '1.0'
|
||||
classified_at: '2026-06-22'
|
||||
classified_by: agent
|
||||
category: project
|
||||
domain: agents
|
||||
secondary_domains: []
|
||||
capability_tags: []
|
||||
business_stake:
|
||||
- technology
|
||||
- automation
|
||||
- product
|
||||
business_mechanics:
|
||||
- coordination
|
||||
- operation
|
||||
294
AGENTS.md
294
AGENTS.md
@@ -1,238 +1,240 @@
|
||||
# can-you-assist - Agent Instructions
|
||||
|
||||
## Worker Role
|
||||
|
||||
Primary worker agent: Grok Build / Grok Code.
|
||||
|
||||
Run from the repository root. When available, start with `grok inspect` to
|
||||
confirm Grok has discovered this `AGENTS.md`, the repo files, and any local
|
||||
`.grok/` configuration. The xAI Build docs say Grok reads the `AGENTS.md`
|
||||
instruction-file family and can inspect discovered instructions, skills,
|
||||
plugins, hooks, and MCPs:
|
||||
|
||||
- https://docs.x.ai/build/overview
|
||||
- https://docs.x.ai/build/features/skills-plugins-marketplaces
|
||||
|
||||
This file is the canonical worker guide for this repo. Do not assume a Claude
|
||||
Code MCP server is available; use the State Hub HTTP API unless the operator
|
||||
explicitly provides another integration.
|
||||
# can-you-assist — Agent Instructions
|
||||
|
||||
## Repo Identity
|
||||
|
||||
**Purpose:** Console-native, backend-agnostic LLM assistant for practical local
|
||||
work from the shell, using user-controlled context, memory, and provider
|
||||
configuration.
|
||||
**Purpose:** Console-native, backend-agnostic LLM helper for practical local shell, repository, filesystem, and note workflows.
|
||||
|
||||
**Domain:** capabilities
|
||||
**Domain:** agents
|
||||
**Repo slug:** can-you-assist
|
||||
**Topic ID:** `64418556-3206-457a-ba29-6884b5b12cf3`
|
||||
**Workplan prefix:** `CYA-WP-`
|
||||
**Command name:** `cya`
|
||||
|
||||
## Product Direction
|
||||
|
||||
`cya` should help users express intent in natural language from a terminal and
|
||||
receive useful, explainable help for command-line tasks. It should be good at
|
||||
repository inspection, file and note workflows, command suggestion, command
|
||||
explanation, and local context summarization.
|
||||
|
||||
Keep these boundaries crisp:
|
||||
|
||||
- `can-you-assist` owns the CLI assistant experience, local context gathering,
|
||||
safety prompts, command explanations, and orchestration of assistance.
|
||||
- `llm-connect` owns provider/backend access. Do not hard-code one LLM vendor
|
||||
as the conceptual foundation.
|
||||
- `phase-memory` owns user-controlled memory, preferences, history, and
|
||||
adaptation. Do not hide memory in opaque hosted state.
|
||||
- State Hub tracks work, coordination, progress, and cross-repo handoffs. It
|
||||
should not become a runtime dependency of the `cya` CLI.
|
||||
---
|
||||
|
||||
## State Hub Integration
|
||||
|
||||
The Custodian State Hub tracks work across all domains. Interact via HTTP REST.
|
||||
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
||||
there is no MCP server for Codex agents.
|
||||
|
||||
| Context | URL |
|
||||
|---------|-----|
|
||||
| Local workstation | `http://127.0.0.1:8000` |
|
||||
| Remote via tunnel | `http://127.0.0.1:18000` |
|
||||
|
||||
### Orient At Session Start
|
||||
### Orient at session start
|
||||
|
||||
```bash
|
||||
# Offline brief - generated by State Hub consistency tooling when available
|
||||
# Offline brief — works without hub connection
|
||||
cat .custodian-brief.md
|
||||
|
||||
# Active workstreams for this domain/topic
|
||||
# Active workstreams for this domain
|
||||
curl -s "http://127.0.0.1:8000/workstreams/?topic_id=64418556-3206-457a-ba29-6884b5b12cf3&status=active" \
|
||||
| python3 -m json.tool
|
||||
|
||||
# Inbox for this repo worker
|
||||
# Check inbox
|
||||
curl -s "http://127.0.0.1:8000/messages/?to_agent=can-you-assist&unread_only=true" \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
Mark a message read:
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||
-H "Content-Type: application/json" -d '{}'
|
||||
```
|
||||
|
||||
### Update Task Status
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "in_progress"}'
|
||||
```
|
||||
|
||||
Allowed task statuses: `todo`, `in_progress`, `done`, `blocked`.
|
||||
|
||||
### Log Progress
|
||||
|
||||
Log progress at session close and after meaningful milestones:
|
||||
### Log progress (required at session close)
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"summary": "what changed",
|
||||
"summary": "what was done",
|
||||
"event_type": "note",
|
||||
"author": "grok",
|
||||
"author": "codex",
|
||||
"workstream_id": "<uuid>",
|
||||
"task_id": "<uuid>"
|
||||
}'
|
||||
```
|
||||
|
||||
Omit `workstream_id` and `task_id` only when there is no applicable item.
|
||||
Omit `workstream_id` / `task_id` when not applicable.
|
||||
|
||||
### Update task status
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"status": "progress"}'
|
||||
# values: wait | todo | progress | done | cancel
|
||||
```
|
||||
|
||||
### Flag a task for human review
|
||||
|
||||
```bash
|
||||
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"needs_human": true, "intervention_note": "reason"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Session Protocol
|
||||
|
||||
Start:
|
||||
**Start:**
|
||||
1. `cat .custodian-brief.md` — domain goal and open workstreams (offline-safe)
|
||||
2. Check inbox: `GET /messages/?to_agent=can-you-assist&unread_only=true`; mark read
|
||||
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
|
||||
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
|
||||
|
||||
1. Run `git status --short`.
|
||||
2. Read `.custodian-brief.md` if present; if absent, use the State Hub API
|
||||
queries above.
|
||||
3. Check the `can-you-assist` inbox and mark read only after acting on a
|
||||
message or carrying it forward in a workplan.
|
||||
4. Scan `workplans/` if it exists. If it does not exist yet, the onboarding
|
||||
workstream should create it.
|
||||
5. Pick the highest-priority active task that is unblocked and update task
|
||||
status before substantial work.
|
||||
**During work:**
|
||||
- Update task statuses in workplan files as tasks progress
|
||||
- Record significant decisions via `POST /decisions/`
|
||||
|
||||
During work:
|
||||
**Close:**
|
||||
1. Update workplan file task statuses to reflect progress
|
||||
2. Log: `POST /progress/` with a summary of what changed
|
||||
3. Note for the custodian operator: after workplan file changes, run from
|
||||
`~/state-hub`:
|
||||
```bash
|
||||
make fix-consistency REPO=can-you-assist
|
||||
```
|
||||
This syncs task status from files into the hub DB.
|
||||
|
||||
- Keep changes small and inspectable.
|
||||
- Treat command execution safety as product behavior: explain destructive or
|
||||
broad filesystem commands and require explicit confirmation before suggesting
|
||||
execution.
|
||||
- Do not commit secrets, API keys, local transcripts, private notes, or hidden
|
||||
memory contents.
|
||||
- Preserve user-controlled memory as a design principle. Prefer explicit,
|
||||
inspectable local files over hidden state.
|
||||
- Record significant design decisions with `POST /decisions/`.
|
||||
---
|
||||
|
||||
Close:
|
||||
## Credential and access routing
|
||||
|
||||
1. Update workplan task statuses to match reality.
|
||||
2. Log a progress event in State Hub.
|
||||
3. Ask the custodian operator to run:
|
||||
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||
|
||||
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||
other credential need belongs to another subsystem. **Do not** message
|
||||
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||
|
||||
### Lookup (do this first)
|
||||
|
||||
```bash
|
||||
cd ~/state-hub && make fix-consistency REPO=can-you-assist
|
||||
warden route find "<describe your need>" --json
|
||||
warden route show <catalog-id> --json
|
||||
```
|
||||
|
||||
That syncs repo workplan files into the State Hub DB and regenerates
|
||||
`.custodian-brief.md`.
|
||||
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||
|
||||
## Current Grok Handoff
|
||||
| Agent runtime | How to orient |
|
||||
| --- | --- |
|
||||
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=can-you-assist` is for coordination, not secret vending |
|
||||
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||
|
||||
State Hub registration has been created for `can-you-assist` under the
|
||||
`capabilities` domain. Look for active workstream
|
||||
`repo-integration-can-you-assist`.
|
||||
### Quick routing table
|
||||
|
||||
First useful worker moves:
|
||||
| I need… | Owner | ops-warden executes? |
|
||||
| --- | --- | --- |
|
||||
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||
| Authorization decision | flex-auth | No — route only |
|
||||
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||
|
||||
1. Read `INTENT.md`, `README.md`, this `AGENTS.md`, and `SCOPE.md`.
|
||||
2. Create `workplans/CYA-WP-0001-console-native-mvp.md` with a focused first
|
||||
implementation slice: CLI skeleton, safe command-assistance flow,
|
||||
llm-connect adapter boundary, phase-memory boundary, and test strategy.
|
||||
3. Keep the first workplan `ready` until the repo state and stack choice are
|
||||
reviewed. Move it to `active` when implementation begins.
|
||||
4. Run or request SBOM ingest from State Hub after the first dependency files
|
||||
exist.
|
||||
5. Register obvious extension points and technical debt once there is code to
|
||||
anchor them.
|
||||
### Anti-patterns (do not do these)
|
||||
|
||||
## Commands
|
||||
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||
|
||||
```bash
|
||||
# Install (editable, for development)
|
||||
pip install -e .
|
||||
### Other capabilities (reuse-surface)
|
||||
|
||||
# Run the assistant
|
||||
cya "your natural language request here"
|
||||
cya --help
|
||||
cya --explain-context "show me what context would be collected"
|
||||
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||
get wrong.
|
||||
|
||||
# Tests (safety-focused, no LLM required)
|
||||
python -m pytest tests/ -q
|
||||
# or: pytest tests/ -q
|
||||
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||
|
||||
# Git / inspection (always useful)
|
||||
git status --short
|
||||
git log --oneline -5
|
||||
rg --files
|
||||
```
|
||||
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||
<!-- Append repo-specific agent instructions below this marker.
|
||||
The state-hub template sync preserves content after this line. -->
|
||||
|
||||
No formal lint or build system yet (ruff is configured in pyproject.toml but not enforced in CI for the first slice). Add `make test` / `make lint` targets in a follow-on when needed.
|
||||
---
|
||||
|
||||
Current primary entry point: `cya` (after editable install).
|
||||
## Workplan Convention (ADR-001)
|
||||
|
||||
## Workplan Convention
|
||||
Work items originate as files in this repo — not in the hub. The hub is a
|
||||
read/cache/index layer that rebuilds from files.
|
||||
|
||||
Work items originate as files in this repo. The hub is a read/cache/index
|
||||
layer that rebuilds from files.
|
||||
**File location:** `workplans/CAN-WP-NNNN-<slug>.md`
|
||||
|
||||
**File location:** `workplans/CYA-WP-NNNN-<slug>.md`
|
||||
**Archived location:** finished workplans may move to
|
||||
`workplans/archived/YYMMDD-CAN-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
|
||||
the completion/archive date; the frontmatter `id` does not change.
|
||||
|
||||
**Archived location:** `workplans/archived/YYMMDD-CYA-WP-NNNN-<slug>.md`
|
||||
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
|
||||
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
|
||||
this only for low-risk work completed directly; create a normal workplan for
|
||||
anything needing analysis, design, approval, dependencies, or multiple phases.
|
||||
|
||||
**Ad hoc tasks:** small opportunistic fixes may use
|
||||
`workplans/ADHOC-YYYY-MM-DD.md` with task ids like
|
||||
`ADHOC-YYYY-MM-DD-T01`. Use this only for low-risk work completed directly.
|
||||
|
||||
Frontmatter:
|
||||
**Frontmatter:**
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: CYA-WP-NNNN
|
||||
id: CAN-WP-NNNN
|
||||
type: workplan
|
||||
title: "..."
|
||||
domain: capabilities
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
owner: codex
|
||||
topic_slug: ...
|
||||
created: "YYYY-MM-DD"
|
||||
updated: "YYYY-MM-DD"
|
||||
state_hub_workstream_id: "<uuid>" # written by fix-consistency - do not edit
|
||||
state_hub_workstream_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
---
|
||||
```
|
||||
|
||||
Task block format:
|
||||
Use `proposed` for a new draft, `ready` after review against current repo
|
||||
state, and `finished` after implementation. `stalled` and `needs_review` are
|
||||
derived health labels, not frontmatter statuses.
|
||||
|
||||
````
|
||||
**Task block format** (one per `##` section):
|
||||
|
||||
```
|
||||
## Task Title
|
||||
|
||||
```task
|
||||
id: CYA-WP-NNNN-T01
|
||||
status: todo | in_progress | done | blocked
|
||||
` ` `task
|
||||
id: CAN-WP-NNNN-T01
|
||||
status: wait | todo | progress | done | cancel
|
||||
priority: high | medium | low
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency - do not edit
|
||||
```
|
||||
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||
` ` `
|
||||
|
||||
Task description text.
|
||||
````
|
||||
```
|
||||
|
||||
Status progression: `todo` -> `in_progress` -> `done` or `blocked`.
|
||||
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
|
||||
|
||||
To create a new workplan:
|
||||
1. Write the file following the format above
|
||||
2. Notify the custodian operator to run `make fix-consistency REPO=can-you-assist`
|
||||
(or send a message to the hub agent via `POST /messages/`)
|
||||
---
|
||||
|
||||
## `cya shell` Command Reference
|
||||
|
||||
Interactive shell sessions are an owned repo capability. Use them for manual
|
||||
operator assistance, not autonomous command execution.
|
||||
|
||||
```bash
|
||||
cya shell # start a stateful shell session
|
||||
cya shell --offline --no-hub # local smoke without hub HTTP calls
|
||||
cya shell --with-history # opt in to capped, redacted history context
|
||||
```
|
||||
|
||||
Session artifacts live at `~/.config/cya/sessions/<session-id>.jsonl`.
|
||||
Shell history is off by default and remains capped/redacted when enabled.
|
||||
|
||||
Slash commands: `/help`, `/explain`, `/hub`, `/hub log "summary"`, `/inbox`,
|
||||
`/inbox read <id>`, `/export-session`, `/learn`, `/exit`.
|
||||
|
||||
State Hub writes are never automatic. `/hub log` requires interactive
|
||||
confirmation; `/inbox read <id>` is the explicit mark-read action.
|
||||
|
||||
12
CLAUDE.md
Normal file
12
CLAUDE.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# can-you-assist — Claude Code Instructions
|
||||
|
||||
@SCOPE.md
|
||||
@.claude/rules/repo-identity.md
|
||||
@.claude/rules/session-protocol.md
|
||||
@.claude/rules/first-session.md
|
||||
@.claude/rules/workplan-convention.md
|
||||
@.claude/rules/stack-and-commands.md
|
||||
@.claude/rules/architecture.md
|
||||
@.claude/rules/repo-boundary.md
|
||||
@.claude/rules/credential-routing.md
|
||||
@.claude/rules/agents.md
|
||||
6
MANIFEST.in
Normal file
6
MANIFEST.in
Normal file
@@ -0,0 +1,6 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include pyproject.toml
|
||||
recursive-include src *.py
|
||||
global-exclude __pycache__
|
||||
global-exclude *.py[co]
|
||||
45
Makefile
Normal file
45
Makefile
Normal file
@@ -0,0 +1,45 @@
|
||||
# Simple developer Makefile for can-you-assist
|
||||
# Focus: easy dev-head install and common tasks
|
||||
|
||||
.PHONY: dev-install install test clean dist version release-prep check-dist
|
||||
|
||||
# Install the package in editable mode from the current source
|
||||
# This is the recommended way to work on the latest code
|
||||
dev-install:
|
||||
pip install -e ".[dev]"
|
||||
|
||||
# Standard install (for comparison / released packages later)
|
||||
install:
|
||||
pip install .
|
||||
|
||||
# Run the test suite
|
||||
test:
|
||||
python3 -m pytest tests/ -q
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -rf build/ dist/ *.egg-info/ src/cya/_version.py
|
||||
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
|
||||
|
||||
# Build distribution packages (sdist + wheel)
|
||||
dist: clean
|
||||
python -m build
|
||||
|
||||
# Show current version (useful after setuptools_scm changes)
|
||||
version:
|
||||
python3 -c "import cya; print(cya.__version__)"
|
||||
|
||||
# Prepare for a release (run tests + build clean artifacts)
|
||||
release-prep: clean test dist
|
||||
@echo ""
|
||||
@echo "Release artifacts ready in dist/"
|
||||
@echo "Next steps: create annotated git tag and push it."
|
||||
|
||||
# Verify the built wheel in an isolated environment
|
||||
check-dist:
|
||||
@echo "Testing wheel in fresh venv..."
|
||||
@python3 -m venv /tmp/cya-dist-check
|
||||
@/tmp/cya-dist-check/bin/pip install dist/can_you_assist-*.whl --quiet
|
||||
@/tmp/cya-dist-check/bin/cya --version
|
||||
@rm -rf /tmp/cya-dist-check
|
||||
@echo "Wheel verification successful."
|
||||
310
MemoryVision.md
310
MemoryVision.md
@@ -108,4 +108,314 @@ This is the correct starting point. Real integration will be done as a subsequen
|
||||
|
||||
---
|
||||
|
||||
## Agentic Memory Research & Profile Directions (2026-05)
|
||||
|
||||
**Date:** 2026-05-28 (ralph start for CYA-WP-0005)
|
||||
**Related:** `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md`, CYA-WP-0003 (activation + retrospection), CYA-WP-0005
|
||||
|
||||
Building directly on the 0002 integration contract (below), the 0003 contextual activation + `cya retrospect` continuous optimization loop, and the post-0004 gap analysis (memory moved from large gap to small-medium), deep research into agentic/self-improving memory architectures was performed and persisted.
|
||||
|
||||
The research synthesizes three canonical patterns for closed self-improving loops in LLM agents (Trial → Evaluation/Feedback → Reflection/Synthesis → Memory Update → Improved future behavior):
|
||||
|
||||
- **Profile 1 — Reflexion-style verbal self-improvement** (lightweight, high-explainability): Store natural-language self-reflections/lessons (leveraging existing `KIND_RETROSPECTION` + `remember_retrospection_outcome` + `cya retrospect`). Preferentially activate them via kinds + activation_context in future turns. Plain-English "verbal reinforcement" that users can inspect and edit.
|
||||
|
||||
- **Profile 2 — Generative-Agents-style hierarchical synthesis**: Treat assistance outcomes, retrospections, and explicit remembers as an episodic stream. Periodic (user-triggered or planner-driven) LLM synthesis produces higher-order abstractions (project conventions, workflow patterns, "in this scope we always...") with citations, stored in stabilized phases. Multi-factor retrieval (recency + importance + relevance + profile/scope match) + the existing activation boost.
|
||||
|
||||
- **Profile 3 — Procedural / meta-policy evolution** (highest leverage, highest guardrail needs): First-class evolvable "how I should behave" rules and procedures as a distinct tier. Meta-reflection (after retrospection or explicit "improve my rules") proposes patches to the procedural layer. Strong dry-run + user veto + safety impact analysis required; changes can only tighten (or maintain) the rule-based risk posture by default.
|
||||
|
||||
Detailed definitions, cya-specific implementation mappings (ports, kinds, orchestrator wiring, safety invariants), capability matrix, and phase-memory interface requirements live in the persisted research document and are executed via CYA-WP-0005 tasks T02–T05.
|
||||
|
||||
This directly addresses several Open Questions above (profile authoring, granularity of project memory, safety signals as first-class memory, observability of memory influence, participation in planning/compaction) while preserving all cya invariants (user control, full provenance/explainability, rule-based safety that memory can only strengthen).
|
||||
|
||||
**Next in this workplan:** T02 formalizes the current post-0003 implementation as explicit **Profile 0** baseline (the stable foundation everything else builds on). T03 adds the full profile definitions + matrix. T04 delivers the concrete optimization suggestions for the phase-memory sister repo.
|
||||
|
||||
See also the full research artifact and CYA-WP-0005 for acceptance criteria and cross-links.
|
||||
|
||||
---
|
||||
|
||||
## Profile 0 Baseline (Post-0003 / Current Shipped)
|
||||
|
||||
**Status:** Shipped and stable as the production memory implementation (CYA-WP-0002 T02 real JSON + CYA-WP-0003 contextual activation + retrospection extensions). This is the explicit foundation on which Profiles 1–3 will be built.
|
||||
|
||||
**Guiding Principle (Profile 0):** Deliver real, user-controlled, contextually activated, longitudinally improving memory *today* using a high-quality local approximation, while keeping the integration seam (profile, kinds, activation_context, provenance, phase hints) completely stable and ready for eventual replacement by full phase-memory planners, graph store, and lifecycle rules. No hidden state; everything is user-inspectable and user-owned.
|
||||
|
||||
### Backing Store & Persistence
|
||||
- Location: `~/.config/cya/memory/<scope>.json` (one file per scope, default "cwd").
|
||||
- Format: Simple list of dict records. Fully human-readable and editable by the user.
|
||||
- Fields per record (typical): `key`, `value`, `ts` (epoch), `scope`, `profile`, `kind`.
|
||||
- User can `cat`, edit, or delete these files at any time; `cya` will respect the changes on next run.
|
||||
- When full phase-memory is wired, this backing will be replaced by the durable graph/event store while preserving the exact same high-level port behavior and return shapes.
|
||||
|
||||
### Public API (the stable cya ↔ phase-memory seam)
|
||||
All entry points live in `src/cya/memory/__init__.py`:
|
||||
|
||||
- **Constants** (standard kinds used by cya and `cya retrospect`):
|
||||
- `KIND_PREFERENCE`
|
||||
- `KIND_RETROSPECTION`
|
||||
- `KIND_INTERACTION_GOAL`
|
||||
|
||||
- **Core functions** (signatures as of 2026-05, all support `profile` for future multi-profile use):
|
||||
- `remember_preference(key, value, scope="cwd", *, profile=None, ttl=None, kind=KIND_PREFERENCE)`
|
||||
- `recall_preferences(scope="cwd", task_class=None, *, kinds=None, profile=None, limit=50, activation_context=None) -> dict`
|
||||
- Returns: `{"items": [...], "provenance": [...], "phase": "fluid", "profile": ..., "note": "..."}`
|
||||
- `forget(scope="cwd", keys=None, *, profile=None)`
|
||||
- `export_memory(scope="cwd", *, profile=None, kinds=None) -> dict` (includes `by_kind` counts, `provenance_summary`, `phases` list)
|
||||
- `remember_retrospection_outcome(...)` — convenience wrapper that chooses the right kind for higher-order memory from reflection sessions.
|
||||
|
||||
**Activation logic (0003):** When `activation_context` (populated by the orchestrator from `ContextEnvelope` with `cwd` + git root) is supplied to `recall_preferences`, items whose `scope` or `profile` matches are boosted to the front of the result list. This makes directory/project-bound memory feel proactive without any user having to explicitly recall it.
|
||||
|
||||
**Retrospection as first-class input (0003):** `cya retrospect` (guided flow) produces records with special kinds that receive preferential activation in future turns. These are the seed for the self-improving loop described in the 2026-05 research.
|
||||
|
||||
**Guaranteed properties of every call:**
|
||||
- Rich `provenance` array always present (source, count, activation_context, etc.).
|
||||
- `phase` hint returned (currently always "fluid" for the local store; will become meaningful once phase-memory lifecycle is wired).
|
||||
- Graceful degradation: on any error the ports log a loud warning to stderr and return safe empty/default values. Never crash the assistance path.
|
||||
- All memory influence is visible via `--explain-context`.
|
||||
|
||||
### Safety & Explainability Invariants (non-negotiable for Profile 0 and all future profiles)
|
||||
- Memory signals are **only additive** to caution. They may append rationale or force confirmation in the rule-based `RiskClassifier`, but they never downgrade a risk level or bypass mandatory explicit user confirmation for non-SAFE commands.
|
||||
- Every memory-influenced response can explain exactly which items were activated, why (activation_context match, kind boost, recency, retrospection provenance), and what phase they came from.
|
||||
- Users can always opt out per-request (`--no-memory` or equivalent) or globally inspect/forget via the ports + future `cya memory` subcommands.
|
||||
|
||||
### Usage Sites in the Current System
|
||||
- `src/cya/orchestrator.py`: Calls `recall_preferences` with activation_context on every assistance request; injects results into the `ContextEnvelope` passed to the LLM; renders memory influence in `--explain-context` panels.
|
||||
- `src/cya/cli/main.py` (retrospect subcommand): Uses `recall_preferences` + `remember_retrospection_outcome` to close the user-driven continuous optimization loop.
|
||||
- Risk classifier: Receives memory context and applies only the "increase caution" rules.
|
||||
|
||||
### Relationship to Profiles 1–3 and phase-memory
|
||||
Profile 0 is deliberately a **complete, usable, production-quality local implementation** of the seam defined in the 0002 T01 contract (refined in 0003). It already delivers the INTENT/SCOPE vision for contextual + longitudinal memory with excellent explainability and zero hidden state.
|
||||
|
||||
Future profiles will layer on top:
|
||||
- Profile 1 adds verbal reflection storage + preferential activation (mostly a small delta on existing retrospection kinds + `cya retrospect`).
|
||||
- Profile 2 adds episodic capture + synthesis passes (new kinds + calls into phase-memory reflection planners).
|
||||
- Profile 3 adds procedural rules + meta-policy evolution (new tier + strong proposal/audit UX + safety guardrails).
|
||||
|
||||
All of them must continue to satisfy the invariants above and must continue to work against the exact same port signatures (or compatible extensions).
|
||||
|
||||
**See:** `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` (research + mappings), CYA-WP-0005 T01–T03, `docs/cya-memory-activation-and-retrospection-concept.md`, current `src/cya/memory/__init__.py` (the reference implementation), and the 0002 integration contract below (the original seam that Profile 0 realizes).
|
||||
|
||||
---
|
||||
|
||||
## Profiles 1–3: Definitions and cya Integration Plans
|
||||
|
||||
**Date:** 2026-05-28 (CYA-WP-0005 T03)
|
||||
**Source:** Synthesized from `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` (the deep research artifact). These are the authoritative working definitions for the self-improving memory profiles. All three preserve cya's non-negotiable contract: user-controlled, fully explainable, safety-first (memory only increases caution), explicit seam to phase-memory.
|
||||
|
||||
The three profiles are ordered by increasing agentic power and implementation cost.
|
||||
|
||||
### Profile 1 — Reflexion-Style Verbal Self-Improvement Loop
|
||||
|
||||
**Status:** Production (CYA-WP-0006). Shipped with guided capture in `cya retrospect`, `cya memory reflections`, explicit compaction, and budget-capped surfacing in responses and `--explain-context`.
|
||||
|
||||
**Intent:** Enable lightweight, high-explainability self-improvement by capturing and preferentially activating natural-language "lessons" and verbal reflections from user interactions and retrospection sessions.
|
||||
|
||||
**Core Loop (condensed):**
|
||||
1. Normal assistance with current Profile 0 activation.
|
||||
2. Outcome capture (accept/revise/reject or explicit `cya retrospect`).
|
||||
3. Verbal reflection generated (1–3 concise lessons in plain English).
|
||||
4. Stored with new or extended `kind` (e.g. `reflection` or `verbal_lesson`) via `remember_retrospection_outcome` / new helper.
|
||||
5. Future recall boosts these kinds + activation_context; they are prepended with high salience.
|
||||
6. Visible in `--explain-context` and final output ("3 verbal reflections influenced this...").
|
||||
|
||||
**cya Mapping (builds directly on 0003):**
|
||||
- ~80% already exists (`KIND_RETROSPECTION`, `remember_retrospection_outcome`, `cya retrospect` flow, kind filter in recall, activation_context boost).
|
||||
- Small delta: lightweight "capture lesson" step at end of retrospect (or auto after notable outcomes); new `kind` values; preferential activation logic tweak.
|
||||
- Orchestrator already has the wiring to surface them.
|
||||
|
||||
**Delta Required:** Minor enhancements to retrospect CLI flow and recall boosting; new `kind` constant(s).
|
||||
|
||||
**Phase-memory Fit:** Minimal for MVP — good native `kinds` filter + provenance. Later: a lightweight "reflection planner" for compaction of duplicate lessons.
|
||||
|
||||
**Safety / Explainability:** Reflections that touch risky patterns still go through the rule-based RiskClassifier. All items carry full provenance. Users can inspect/edit/forget them directly.
|
||||
|
||||
**See also:** Research doc Variation 1, Shinn et al. (Reflexion), current `docs/cya-memory-activation-and-retrospection-concept.md`.
|
||||
|
||||
### Profile 2 — Generative-Agents-Style Hierarchical Memory Stream + Synthesis
|
||||
|
||||
**Intent:** Move from passive storage to active synthesis of higher-order knowledge (project conventions, recurring workflows, "in this scope we always...") so the assistant becomes meaningfully smarter over time with citations back to source events.
|
||||
|
||||
**Core Loop:**
|
||||
1. Episodic capture of every turn/outcome/retrospection (structured records with ts, kind, scope, activation_context, payload, provenance).
|
||||
2. Periodic synthesis (user-triggered via `cya retrospect --synthesize`, or phase-memory planner): LLM clusters recent fluid memories and produces abstractions with citations.
|
||||
3. Store synthesized items with elevated phase hint ("stabilized") and dedicated kinds (`KIND_SYNTHESIZED_CONVENTION`, `KIND_PROJECT_PATTERN`, etc.).
|
||||
4. Retrieval uses multi-factor scoring (recency + importance + relevance + profile/scope match) + existing activation boost.
|
||||
5. Hierarchy emerges naturally (raw → synthesized → higher-order).
|
||||
6. Compaction / phase transitions proposed with dry-run + user review.
|
||||
|
||||
**cya Mapping:**
|
||||
- Local JSON already provides a usable episodic stream.
|
||||
- `export_memory` + recall with activation_context already return provenance.
|
||||
- Orchestrator already injects memory into ContextEnvelope.
|
||||
- Extend `retrospect` to offer synthesis as an explicit option.
|
||||
- Add lightweight importance scoring on remember.
|
||||
- Synthesized items flow through the same recall path (different kinds/phases).
|
||||
|
||||
**Delta Required:** Synthesis entrypoint (or call into phase-memory planner), new kinds, importance scoring, UI affordance in retrospect for "synthesize patterns".
|
||||
|
||||
**Phase-memory Fit (strong alignment):**
|
||||
- Synthesis / reflection planner entrypoint (cya can request "run reflection pass").
|
||||
- Structured objects with citation/provenance fields.
|
||||
- Phase transition proposals (fluid → stabilized) with dry-run diffs.
|
||||
- Multi-factor retrieval API that cya can parameterize.
|
||||
|
||||
**Safety / Explainability:** Every synthesized item carries machine + human readable citations. All influence is visible in `--explain-context`. Synthesis proposals are always reviewable (dry-run first).
|
||||
|
||||
**See also:** Research doc Variation 2, Park et al. (Generative Agents), MemoryVision phases section.
|
||||
|
||||
### Profile 3 — Procedural / Meta-Policy Evolution (Aspirational)
|
||||
|
||||
**Intent:** Allow the assistant to evolve its own high-level "how I should behave" rules and procedures as first-class, auditable, evolvable memory — the highest-leverage form of self-improvement, with the strongest guardrails.
|
||||
|
||||
**Core Loop:**
|
||||
1. Base behavior driven by a dedicated tier of procedural memory items (`procedural_rule`, `meta_policy`, `explanation_strategy`, `safety_tuning`).
|
||||
2. Meta-reflection (after retrospect or explicit "improve my rules"): LLM reviews recent outcomes + current rules + safety incidents and proposes patches.
|
||||
3. Proposal + audit: phase-memory / cya presents structured diff ("+1 rule, safety impact: tightens") for user review/edit/approve/veto.
|
||||
4. On approval: rule stored with high stability/phase and becomes active in future activation, prompt construction, and risk hints.
|
||||
5. Guardrails: changes are additive or tightening only by default; every rule has provenance + last-review date; RiskClassifier treats procedural items as strong "force confirmation" signals.
|
||||
|
||||
**cya Mapping:**
|
||||
- New dedicated kinds + `remember_procedural_rule` helper.
|
||||
- New `cya improve-rules` (or retrospect extension) that triggers meta-reflection.
|
||||
- Orchestrator / safety layer gives procedural items highest priority for injection.
|
||||
- Export/inspect surfaces the procedural layer prominently.
|
||||
- Tight integration with existing rule-based RiskClassifier.
|
||||
|
||||
**Delta Required:** New kinds + helper, meta-reflection flow + UI, prominent procedural layer in export/explain, stronger safety injection.
|
||||
|
||||
**Phase-memory Fit (most demanding):**
|
||||
- First-class procedural memory collection + dedicated policy evolution planner.
|
||||
- Proposal/diff semantics with structured review objects (dry-run).
|
||||
- Explicit meta-reflection hooks with bounded context.
|
||||
- Safety/policy gateway enforcing "cannot weaken risk posture without override".
|
||||
- Versioning/rollback for the procedural layer.
|
||||
- Profile-level "aggressiveness" setting (conservative / balanced / bold).
|
||||
|
||||
**Safety / Explainability:** Highest bar. All proposals declare safety impact. User veto is mandatory for anything that could relax posture. Full audit trail.
|
||||
|
||||
**See also:** Research doc Variation 3, LangMem procedural, A-Mem, LEGOMem, cya `risk/classifier.py`.
|
||||
|
||||
### Profile Capability Matrix
|
||||
|
||||
| Aspect | Profile 0 (baseline) | Profile 1 (Verbal Reflexion) | Profile 2 (Hierarchical Synthesis) | Profile 3 (Procedural Evolution) |
|
||||
|-------------------------------|---------------------------------------|------------------------------------------|---------------------------------------------|-----------------------------------------------|
|
||||
| Primary new kinds | preference, retrospection, interaction_goal | reflection / verbal_lesson | synthesized_convention, project_pattern | procedural_rule, meta_policy, safety_tuning |
|
||||
| Activation model | scope/profile boost + recency | + kind boost for reflections | + multi-factor (recency + importance + relevance) | + highest priority for procedural items |
|
||||
| Synthesis / Reflection | None (user only via retrospect) | Lightweight verbal lessons (user or light auto) | Periodic LLM synthesis with citations (planner or user-triggered) | Meta-reflection proposes rule patches (strong audit) |
|
||||
| Procedural support | None | None | None | First-class tier + evolution planner |
|
||||
| User control surface | remember / forget / export / retrospect | + "capture lesson" in retrospect | "synthesize patterns" option + review dry-runs | Full proposal review, veto, rollback |
|
||||
| Safety impact | Additive only (never downgrades) | Same + reflections can force confirmation | Same + synthesis proposals carry impact tags | Highest: cannot relax risk without explicit override |
|
||||
| cya implementation effort | Already shipped | Small (retrospect step + kind boost) | Medium (synthesis entrypoint + importance) | High (new flow + UI + safety injection) |
|
||||
| phase-memory effort (key) | N/A (local approximation) | Minimal (kinds + provenance) | High (synthesis planner, phase proposals, multi-factor retrieval) | Highest (procedural planner, diff/review objects, policy gateway) |
|
||||
| Explainability | Full provenance + --explain-context | + actual reflection text | + citations on every synthesized item | + full audit trail on every rule change |
|
||||
|
||||
**Notes on the matrix:** Costs are relative. Profile 1 is designed to deliver quick wins on top of existing 0003 machinery. Profiles 2 and 3 require increasing collaboration with phase-memory planners and stronger dry-run/audit primitives.
|
||||
|
||||
### Handoff & Next Steps (within CYA-WP-0005)
|
||||
- T04 turns the suggestions section of the research doc into the polished, standalone `docs/phase-memory-optimization-suggestions.md` (or equivalent) for sister-repo coordination.
|
||||
- T05+ explore minimal implementation spikes (starting with Profile 1) only after the definitions and phase-memory feedback are reviewed.
|
||||
- All profile work must preserve the Profile 0 invariants documented above.
|
||||
|
||||
See the full research artifact for deeper citations (Shinn, Park, LangMem, etc.) and the detailed phase-memory feedback list.
|
||||
|
||||
---
|
||||
|
||||
## cya ↔ phase-memory Integration Contract (CYA-WP-0002 T01)
|
||||
|
||||
**Date:** 2026-05-26 (ralph iter 1)
|
||||
**Status:** Draft — produced during T01 review; to be validated with phase-memory owners.
|
||||
**Parties:** `cya` (capabilities domain, consumer for terminal assistance) and `phase-memory` (markitect domain, provider of phase-aware runtime planning, lifecycle, activation, and low-level ports).
|
||||
|
||||
### Scope for CYA-WP-0002 (first real slice)
|
||||
- Memory kinds: primarily `preference` (user prefs, workflow patterns, "never auto-run" standing rules) + basic project/cwd context.
|
||||
- Scopes: `cwd` (default), project/directory.
|
||||
- Phases: ephemeral/fluid for session-conversation prefs; stabilized (with dry-run + review) for user-declared long-term prefs per lifecycle-rules.
|
||||
- Operations: remember, recall (with provenance + explainable plan), forget (scoped), export (for transparency and --explain-context).
|
||||
- Non-goals (this slice): full 9 kinds, embeddings/SemanticIndex, durable kontextual graph, voice, full profile authoring.
|
||||
|
||||
### Refined Port Signatures (cya seam)
|
||||
These replace/extend the T05 no-op signatures. Implementations in T02+ will delegate to `phase_memory` (ports, planner, lifecycle, runtime or high-level sugar).
|
||||
|
||||
```python
|
||||
def remember_preference(
|
||||
key: str,
|
||||
value: Any,
|
||||
scope: str = "cwd",
|
||||
*,
|
||||
profile: str | None = None, # e.g. "cya-assistant-v1" or user profile id
|
||||
ttl: str | None = None, # e.g. "30d" or phase hint
|
||||
) -> None: ...
|
||||
|
||||
def recall_preferences(
|
||||
scope: str = "cwd",
|
||||
task_class: str | None = None,
|
||||
*,
|
||||
kinds: list[str] | None = None, # ["preference", "task"] etc.
|
||||
profile: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
# Returns: {"items": [...], "provenance": [...], "dry_run_plan": {...}, "phase": "fluid", "profile": ...}
|
||||
...
|
||||
|
||||
def forget(scope: str = "cwd", keys: list[str] | None = None, *, profile: str | None = None) -> None: ...
|
||||
|
||||
def export_memory(scope: str = "cwd", *, profile: str | None = None) -> dict[str, Any]:
|
||||
# Includes status, phase info, provenance summary, policy notes for explain.
|
||||
...
|
||||
```
|
||||
|
||||
All calls must be non-blocking for the assistance path; failures → graceful empty + stderr warn (current behavior preserved).
|
||||
|
||||
### Ownership & Responsibilities
|
||||
- **cya owns**: the seam (these 4 functions + wiring in orchestrator/cli for context + explain), safety integration (memory signals feed rule-based RiskClassifier but never bypass confirmation), user-visible explainability (provenance rendered in --explain-context and final output), graceful degradation.
|
||||
- **phase-memory owns**: the profile execution planner, phase/lifecycle/retention/compaction planners (dry-run first), low-level ports (MemoryGraphStore, MemoryEventLog, PolicyGateway, ...), adapter orchestration, Markitect contract interop, provenance/audit in results.
|
||||
- Boundary: cya calls high-level or planner entry points; never mutates graph directly or bypasses policy/review gates.
|
||||
|
||||
### Gaps & Required Follow-up
|
||||
- phase-memory pilot maturity for "preference" kind high-level sugar (or cya builds minimal adapter on graph/event for T02).
|
||||
- Shared cya profile contract (markitect.memory.profile.v1) for assistance prefs.
|
||||
- Standardized provenance envelope for cya explain rendering.
|
||||
- T04: memory signals must still trigger mandatory confirmation for dangerous commands.
|
||||
- T05/T06: fake adapter for tests, docs with before/after, State Hub extension points.
|
||||
|
||||
**References:** phase-memory/docs/{architecture.md, markitect-interop.md, lifecycle-rules.md, local-persistence.md, ports.py, planner.py}; cya src/cya/memory/__init__.py (seam), orchestrator.py; CYA-WP-0002 T02–T06; MemoryVision success criteria.
|
||||
|
||||
## What CYA-WP-0003 Delivered (Contextual Activation + Retrospection)
|
||||
|
||||
**Date:** 2026-05-27
|
||||
|
||||
0003 moved memory from "real but mostly passive" to **proactively useful and user-steerable over time**.
|
||||
|
||||
### Key Additions
|
||||
- **Directory / Project-bound Memory Activation (T03)**
|
||||
Memory is now automatically activated based on the current working directory and git root. The orchestrator passes `activation_context`, and `recall_preferences` boosts relevant items. Activation is fully visible in `--explain-context` with provenance.
|
||||
|
||||
- **Retrospection as a First-Class Interaction Pattern (T04)**
|
||||
Added the `cya retrospect` subcommand — a guided terminal reflection session. Users review recent memory usage, reflect, and explicitly record:
|
||||
- Interaction goals
|
||||
- Refined preferences
|
||||
- Safety rules
|
||||
These are stored with special kinds (`retrospection`, `interaction_goal`) and get preferential treatment in future activations.
|
||||
|
||||
- **Port & Data Model Extensions (T02)**
|
||||
- `kind` parameter on `remember_preference`
|
||||
- `activation_context` support on `recall_preferences`
|
||||
- `remember_retrospection_outcome()` helper
|
||||
- Improved `export_memory` with kind filtering and `by_kind` summary
|
||||
- Full backward compatibility maintained.
|
||||
|
||||
- **Tests & Observability (T05)**
|
||||
Comprehensive tests for activation boosting, retrospection records, provenance, and graceful degradation.
|
||||
|
||||
### Impact
|
||||
A user can now:
|
||||
- Teach `cya` once in a project and have it automatically remember those preferences.
|
||||
- Run `cya retrospect` periodically to steer how the assistant behaves in the future.
|
||||
- See exactly which memories influenced any response.
|
||||
|
||||
These features directly realize the "Personalized Console Helper" and "continuous optimization" vision from INTENT.md while staying within the explicit port seam.
|
||||
|
||||
**Next logical deepening:** When `phase-memory` exposes stable high-level activation + profile APIs, the local JSON implementation can be replaced while keeping the same user experience and `cya retrospect` flow.
|
||||
|
||||
---
|
||||
|
||||
This document is distinct from the Intent-vs-Scope gap analysis. It is the forward-looking vision for how memory will evolve in `cya` once real `phase-memory` integration begins. It should be updated as integration work progresses and as phase-memory itself matures.
|
||||
235
README.md
235
README.md
@@ -16,17 +16,83 @@ usable after `pip install -e .`:
|
||||
- `cya "your request in plain English"`
|
||||
- `cya --explain-context "..."` — shows exactly what local context would be sent
|
||||
- Automatic rule-based risk classification with mandatory confirmation for anything destructive, privileged, mass-edit, or network-affecting
|
||||
- All LLM interaction flows through a documented `LLMAdapter` seam (currently a deterministic fake; ready for real `llm-connect`)
|
||||
- All LLM interaction flows through a documented `LLMAdapter` seam (`FakeLLMAdapter` by default; real `llm-connect` when configured)
|
||||
|
||||
## Installation (development)
|
||||
## Installation
|
||||
|
||||
### 1. Install from Development Head (Recommended for Daily Use)
|
||||
|
||||
Stay on the absolute latest code with one command:
|
||||
|
||||
```bash
|
||||
git clone <this-repo>
|
||||
git clone https://github.com/worsch/can-you-assist.git
|
||||
cd can-you-assist
|
||||
pip install -e .
|
||||
cya --help
|
||||
make dev-install
|
||||
```
|
||||
|
||||
Or the direct git method (no permanent clone needed):
|
||||
|
||||
```bash
|
||||
pip install "git+https://github.com/worsch/can-you-assist.git@main#egg=can-you-assist[dev]"
|
||||
```
|
||||
|
||||
After installing, `cya --version` will show a development version (e.g. `0.2.0.dev48+gf500a35...`).
|
||||
|
||||
### 2. Install a Released Version (Future)
|
||||
|
||||
Once we publish official releases:
|
||||
|
||||
```bash
|
||||
pip install can-you-assist
|
||||
# or a specific version
|
||||
pip install "can-you-assist>=0.3.0"
|
||||
```
|
||||
|
||||
See `docs/release-process.md` for how releases are cut.
|
||||
|
||||
### Updating
|
||||
|
||||
```bash
|
||||
cd can-you-assist
|
||||
git pull
|
||||
make dev-install
|
||||
```
|
||||
|
||||
## LLM backend (configured vs offline)
|
||||
|
||||
By default `cya` uses a deterministic **offline** adapter (no API keys, no network).
|
||||
For real inference, configure llm-connect:
|
||||
|
||||
```bash
|
||||
# 1. Install llm-connect (sibling checkout)
|
||||
pip install -e ~/llm-connect
|
||||
|
||||
# 2. Route credentials — do not commit keys
|
||||
warden route find "OpenRouter API key" --json
|
||||
|
||||
# 3. Export key and configure cya
|
||||
export OPENROUTER_API_KEY="..." # from OpenBao / operator path
|
||||
mkdir -p ~/.config/cya
|
||||
cat > ~/.config/cya/config.toml <<'EOF'
|
||||
[llm]
|
||||
adapter = "connect"
|
||||
backend = "openrouter"
|
||||
model = "anthropic/claude-sonnet-4"
|
||||
EOF
|
||||
|
||||
cya "show me the recent git history for this repo"
|
||||
```
|
||||
|
||||
Force offline mode anytime (tests, CI, air-gapped):
|
||||
|
||||
```bash
|
||||
cya --offline "your request"
|
||||
# or: CYA_LLM_ADAPTER=fake cya "..."
|
||||
```
|
||||
|
||||
See `docs/llm-connect-integration.md` for the full mapping, session context budget,
|
||||
and optional `.cya.toml` project overrides.
|
||||
|
||||
## Usage examples
|
||||
|
||||
```bash
|
||||
@@ -43,6 +109,60 @@ cya --explain-context "explain the changes in the last commit"
|
||||
The output includes a structured suggestion, rationale, and (when relevant) a
|
||||
clear preview + confirmation prompt. Nothing executes without your explicit yes.
|
||||
|
||||
## Interactive Shell Sessions
|
||||
|
||||
Start a stateful console session when you want continuity across turns:
|
||||
|
||||
```bash
|
||||
cya shell
|
||||
cya shell --offline --no-hub # local smoke / air-gapped mode
|
||||
cya shell --with-history # opt in to capped, redacted shell history
|
||||
```
|
||||
|
||||
Each session writes a user-owned JSONL artifact under:
|
||||
|
||||
```text
|
||||
~/.config/cya/sessions/<session-id>.jsonl
|
||||
```
|
||||
|
||||
Useful slash commands:
|
||||
|
||||
```text
|
||||
/help show shell commands
|
||||
/explain show last turn context, risk, history, hub, hints
|
||||
/hub show State Hub workstreams and inbox orientation
|
||||
/hub log "summary" post a progress note after confirmation
|
||||
/inbox show unread State Hub messages
|
||||
/export-session write a redacted JSON session summary
|
||||
/learn capture Profile 1 reflections now
|
||||
/exit close the session
|
||||
```
|
||||
|
||||
Shell history is off by default. `--with-history` or `[shell_history]` config
|
||||
includes at most 50 recent lines from `$HISTFILE` or common shell history files,
|
||||
redacts secret-like values, and exposes provenance in `/explain`. One-shot
|
||||
`cya "..."` requests remain history-free by default.
|
||||
|
||||
Example transcript:
|
||||
|
||||
```text
|
||||
$ cya shell --offline --no-hub
|
||||
cya> summarize recent git changes
|
||||
... standard orchestrator response ...
|
||||
cya> /explain
|
||||
... context envelope, memory, shell history status, hub summary, and hints ...
|
||||
cya> /export-session
|
||||
Exported session summary: ~/.config/cya/sessions/cya-...-summary.json
|
||||
cya> /exit
|
||||
Session saved: ~/.config/cya/sessions/cya-...jsonl
|
||||
```
|
||||
|
||||
State Hub writes never happen automatically. `/hub log` and `/inbox read` are
|
||||
explicit operator actions; `/hub log` also asks for confirmation.
|
||||
|
||||
See `docs/cya-interactive-shell-session-design.md` and
|
||||
`docs/cya-shell-operator-session.md` for the full design and operator example.
|
||||
|
||||
## Safety (core product behavior)
|
||||
|
||||
- Genuine rule-based assessment is the primary mechanism.
|
||||
@@ -53,13 +173,103 @@ clear preview + confirmation prompt. Nothing executes without your explicit yes.
|
||||
|
||||
See the risk classifier tests and workplan T03 for the exact rules and invariants.
|
||||
|
||||
## Memory (T02 + T03 + T04 + 0003)
|
||||
|
||||
`cya` has real, user-controlled, context-aware memory that improves over time.
|
||||
|
||||
### Automatic Directory/Project-Bound Activation (T03)
|
||||
|
||||
Memory scoped to a directory or project is automatically activated when you work there.
|
||||
|
||||
```bash
|
||||
# In your project directory, teach cya your preferences once
|
||||
cya "remember that I always want the short git status with branch info"
|
||||
|
||||
# Later, in the same directory (or any subdirectory), cya will use it automatically
|
||||
cya "show me the recent changes"
|
||||
|
||||
# No need to restate your preference — it is activated based on the working directory
|
||||
```
|
||||
|
||||
See exactly what memory influenced a response:
|
||||
```bash
|
||||
cya --explain-context "show me the recent changes"
|
||||
```
|
||||
|
||||
### Structured Retrospection & Continuous Improvement (T04)
|
||||
|
||||
Run a guided reflection session to review how memory was used and explicitly set goals for future interactions.
|
||||
|
||||
```bash
|
||||
cya retrospect
|
||||
```
|
||||
|
||||
During the session `cya` will:
|
||||
- Show recent memory items that were activated.
|
||||
- Help you reflect on what worked or didn't.
|
||||
- Let you record new **interaction goals** (e.g. "be more concise", "always show one safe alternative for destructive commands").
|
||||
- Optionally capture **1–3 verbal lessons** (Profile 1) with guided prompts, preview, and confirmation.
|
||||
|
||||
Example Profile 1 flow at the end of `cya retrospect`:
|
||||
|
||||
```
|
||||
Capture 1–3 verbal lessons from this session? (y/n) y
|
||||
What went well that you want to remember? (or 'skip') Safety warnings were clear
|
||||
What should cya remember for next time? (or 'skip') Always suggest git status first
|
||||
What should cya avoid in this scope? (or 'skip') skip
|
||||
|
||||
Preview — verbal lessons to save
|
||||
1. [went well] Safety warnings were clear
|
||||
2. [remember] Always suggest git status first
|
||||
|
||||
Save these lessons? (y/n) y
|
||||
Saved 2 verbal reflection(s) (Profile 1).
|
||||
```
|
||||
|
||||
These goals and lessons are stored as first-class memory and will influence future activations and responses.
|
||||
|
||||
### Inspecting and Controlling Memory
|
||||
|
||||
All memory is stored in plain, user-editable JSON:
|
||||
```bash
|
||||
~/.config/cya/memory/<scope>.json
|
||||
```
|
||||
|
||||
Useful commands:
|
||||
```bash
|
||||
cya --explain-context "..." # See exactly what memory was activated and why
|
||||
cya memory reflections # List verbal reflections for the current scope
|
||||
cya memory reflections --json # Export reflections as JSON
|
||||
# (You can also use the memory ports directly in Python if you want to script it.)
|
||||
```
|
||||
|
||||
Memory also feeds the safety layer: a "never auto-run" preference you set during retrospection will still force mandatory confirmation.
|
||||
|
||||
### Architecture Notes
|
||||
- Memory lives behind explicit ports in `src/cya/memory/__init__.py`.
|
||||
- Activation is automatic based on cwd + git root (with full provenance).
|
||||
- Retrospection outcomes are stored with special kinds (`retrospection`, `interaction_goal`) and get preferential treatment in future context building.
|
||||
- Everything is designed to be replaced/enriched by a full `phase-memory` implementation later (see MemoryVision.md).
|
||||
- **Profile 0** (post-0003 local JSON + activation + retrospection loop) is the stable foundation.
|
||||
- **Profile 1** (verbal reflections) is production-ready as of CYA-WP-0006: guided capture in `cya retrospect`, `cya memory reflections`, compaction, and surfacing in responses / `--explain-context`.
|
||||
- Profiles 2–3 (hierarchical synthesis, procedural rules) remain roadmap items — see MemoryVision.md.
|
||||
|
||||
See:
|
||||
- `docs/cya-memory-activation-and-retrospection-concept.md` (the T01 design)
|
||||
- `workplans/CYA-WP-0003-...md`
|
||||
- `src/cya/memory/__init__.py`
|
||||
- `MemoryVision.md` for the long-term phase-memory vision
|
||||
|
||||
All memory usage is visible, explainable, and under your control. Nothing is hidden or opaque.
|
||||
|
||||
## Architecture & boundaries (important)
|
||||
|
||||
- `can-you-assist` (this repo): CLI, context collection, safety, orchestration.
|
||||
- `llm-connect`: Provider access, config, token counting, structured responses.
|
||||
All interaction goes through `cya/llm/adapter.py` (`LLMAdapter` Protocol).
|
||||
- `phase-memory`: Durable, user-controlled memory. This slice has only
|
||||
strictly minimal explicit no-op ports (see `cya/memory/__init__.py`).
|
||||
- `phase-memory`: Durable, user-controlled memory. Real (persisting) implementation
|
||||
lives behind the explicit ports in `cya/memory/__init__.py` (T02). Signals also flow
|
||||
into the rule-based risk layer (T04).
|
||||
|
||||
See `workplans/CYA-WP-0001-console-native-mvp.md` for the full task breakdown,
|
||||
decisions, and integration guide.
|
||||
@@ -67,9 +277,14 @@ decisions, and integration guide.
|
||||
## Development
|
||||
|
||||
```bash
|
||||
pip install -e .
|
||||
pytest tests/ -q
|
||||
cya "..." # manual verification
|
||||
# Recommended one-liner (see Installation section above)
|
||||
make dev-install
|
||||
pip install -e ~/llm-connect # optional, for live inference
|
||||
|
||||
pytest tests/ -q # offline mocks only; no API keys
|
||||
pytest -m llm_live # manual live check (requires OPENROUTER_API_KEY)
|
||||
cya --offline "..." # manual verification without network
|
||||
make version # show current dev version
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
104
SCOPE.md
104
SCOPE.md
@@ -6,19 +6,31 @@
|
||||
|
||||
It allows users to express intent in natural language from the terminal and receive safe, explainable, context-aware assistance while keeping memory, history, preferences, and adaptation under explicit user control.
|
||||
|
||||
## Current Status (Post MVP Slice)
|
||||
## Current Status (Post CYA-WP-0007 Interactive Shell Session Slice)
|
||||
|
||||
The first narrow implementation slice (CYA-WP-0001) has been delivered. A working `cya` tool now exists that can be installed with `pip install -e .`.
|
||||
Four implementation slices have been delivered:
|
||||
|
||||
Core capabilities implemented:
|
||||
- Natural language request handling via a clean Typer CLI.
|
||||
- Bounded, transparent, non-recursive local context collection (cwd + git + environment + explicit files).
|
||||
- Genuine rule-based risk classification with mandatory terminal confirmation for anything above "safe".
|
||||
- Stable `LLMAdapter` Protocol boundary (currently satisfied by a deterministic `FakeLLMAdapter`).
|
||||
- Strictly minimal explicit no-op ports for future `phase-memory` integration.
|
||||
- A small orchestrator that coordinates the above.
|
||||
- **CYA-WP-0001 (Console-Native MVP)**: Core CLI, bounded context collection, rule-based safety with mandatory confirmation, LLMAdapter Protocol seam, basic orchestrator.
|
||||
- **CYA-WP-0002 (Memory Integration)**: Real user-controlled, persisting memory (scoped JSON) behind explicit ports, wired into context and safety.
|
||||
- **CYA-WP-0003 (Contextual Activation & Retrospection)**: Directory/project-bound automatic memory activation, `cya retrospect` guided reflection sessions, retrospection outcomes feeding future behavior (continuous user-driven optimization loop).
|
||||
- **Profile 0 baseline (post-0003, formalized in CYA-WP-0005 T02)**: The current shipped memory implementation (local JSON + kinds + activation_context + provenance + retrospection helper) is now explicitly documented as **Profile 0** — the stable, high-quality foundation for future self-improving profiles 1–3. See MemoryVision.md for the full baseline description.
|
||||
- **CYA-WP-0005 (Agentic Memory Profiles + first self-improvement capability)**: Complete profile model (Profile 0 baseline + detailed definitions + integration plans + Capability Matrix for Profiles 1–3) plus initial **Profile 1** delivery. **CYA-WP-0006** hardened Profile 1 to production quality: guided 1–3 lesson capture with preview in `cya retrospect`, `cya memory reflections`, near-duplicate compaction, and surfacing in responses / `--explain-context`. See MemoryVision.md and `docs/CYA-WP-0006-profile-1-gap-checklist.md`.
|
||||
- **CYA-WP-0004 (Dev-Head Install & Release Packaging)**: Reliable installation from development head (`make dev-install`, direct `git+` installs), dynamic versioning via `setuptools_scm`, clean distribution package building (`python -m build` + verification), lightweight release process, and supporting documentation/Makefile.
|
||||
- **CYA-WP-0007 (Interactive Shell Session)**: `cya shell` REPL, session JSONL artifacts, optional capped/redacted shell history, read-only State Hub orientation, explicit hub slash-command writes, end-session learning/export, and deterministic weakness hints.
|
||||
|
||||
All LLM interaction flows through the documented adapter seam. No production path bypasses it.
|
||||
Core capabilities now include:
|
||||
- Natural language request handling via clean Typer CLI.
|
||||
- Bounded, transparent local context collection.
|
||||
- Genuine rule-based (memory-aware) risk classification with mandatory confirmation.
|
||||
- Stable `LLMAdapter` Protocol with `LLMConnectAdapter` behind the same factory when configured.
|
||||
- Real, user-controlled, contextually activated memory (Profile 0: directory/project scoped local JSON with kinds, activation_context, provenance, and retrospection outcomes as higher-order memory).
|
||||
- Automatic memory activation based on working directory/git root.
|
||||
- `cya retrospect` for structured reflection and goal setting, with production Profile 1 verbal lesson capture, review (`cya memory reflections`), and compaction.
|
||||
- Full developer workflow: dev-head install, testing, building distribution packages, and a documented release process.
|
||||
- Transparent, inspectable behavior via `--explain-context` and shell `/explain`.
|
||||
- Stateful `cya shell` sessions with local JSONL artifacts, slash commands, optional history context, hub orientation, export, and opt-in learning.
|
||||
|
||||
All LLM interaction flows through the documented adapter seam. Memory flows through explicit ports. Packaging and distribution are now first-class concerns with a clear path forward. No production path bypasses the defined boundaries.
|
||||
|
||||
## Owns
|
||||
|
||||
@@ -28,8 +40,9 @@ All LLM interaction flows through the documented adapter seam. No production pat
|
||||
- Safety layer: rule-based risk assessment + mandatory explicit confirmation flow.
|
||||
- Orchestration of the request → context → safety → LLM adapter → response pipeline.
|
||||
- The stable `LLMAdapter` Protocol and the contract for how `cya` talks to LLM backends.
|
||||
- Explicit, minimal integration points (ports) for `phase-memory`.
|
||||
- Transparent, inspectable behavior (especially via `--explain-context`).
|
||||
- Explicit, now real (persisting) integration with user-controlled memory via `phase-memory` ports.
|
||||
- Transparent, inspectable behavior (especially via `--explain-context` and shell `/explain`).
|
||||
- Interactive `cya shell` sessions: prompt loop, session state, local artifacts, slash commands, optional shell-history context, hub orientation, export, and end-session learning prompts.
|
||||
- User-facing documentation, examples, and safety guarantees for the CLI tool.
|
||||
|
||||
## Does Not Own
|
||||
@@ -40,60 +53,69 @@ All LLM interaction flows through the documented adapter seam. No production pat
|
||||
- Autonomous or background execution of commands without explicit user confirmation.
|
||||
- Deep repository indexing, embeddings, or large-scale content analysis (explicit non-goal of the first slice).
|
||||
- Voice, speech, phone-bridge, or non-terminal interfaces (future work).
|
||||
- Packaging, distribution, or multi-platform installers beyond basic editable install.
|
||||
- Long-lived conversational REPL or session state (one-shot + very lightweight session only).
|
||||
- Production PyPI publishing and automated release CI (documented process and local tooling exist; actual publication is future work).
|
||||
- Hosted, team-shared, or background session state; `cya shell` artifacts remain local and user-owned.
|
||||
|
||||
## Integrates With
|
||||
|
||||
| Project | Responsibility | Integration Style |
|
||||
|---------------|-----------------------------------------|------------------------------------|
|
||||
| `llm-connect` | Provider access, config, token counting, structured responses | Stable `LLMAdapter` Protocol |
|
||||
| `phase-memory`| User-controlled memory, preferences, history | Explicit thin ports (currently no-op) |
|
||||
| `phase-memory`| User-controlled memory, preferences, history, profiles, and activation planning | Explicit ports with real (local JSON + contextual activation + retrospection) implementation; long-term target is deeper profile-driven integration |
|
||||
| State Hub | Work tracking, decisions, coordination | HTTP REST (non-runtime) |
|
||||
|
||||
## MVP Scope (CYA-WP-0001)
|
||||
## Current Delivered Scope (Post CYA-WP-0005 T05)
|
||||
|
||||
What was explicitly in scope for the first slice:
|
||||
Significant slices have been delivered beyond the original MVP (most recently the full profile model and first self-improving capability from CYA-WP-0005):
|
||||
|
||||
- A functional `cya` CLI that accepts natural language requests.
|
||||
- Safe, bounded context collection with full transparency (`--explain-context`).
|
||||
- Rule-based safety classification as the primary mechanism, with mandatory confirmation.
|
||||
- A clean, documented adapter boundary for future real LLM backends.
|
||||
- Strictly minimal memory ports (no hidden state, no local JSON store).
|
||||
- Basic orchestrator that ties the pieces together.
|
||||
- Test coverage focused on safety invariants and context rules.
|
||||
- Clear public boundaries and extension points.
|
||||
- Full console-native CLI with rich output.
|
||||
- Context-aware, directory/project-bound memory activation.
|
||||
- User-driven retrospection and continuous optimization loops (`cya retrospect`).
|
||||
- Real, inspectable, user-controlled memory with strong explainability and safety integration.
|
||||
- Complete developer workflow: installation from dev head, testing, building distribution packages, and a documented release process.
|
||||
- Packaging and distribution now treated as first-class concerns (with registered future work).
|
||||
|
||||
## Explicitly Out of Scope (MVP and Near-Term)
|
||||
See the individual workplans for detailed scope per slice.
|
||||
|
||||
- Any durable or sophisticated memory implementation.
|
||||
- Real `llm-connect` client (only the contract + fake exists).
|
||||
- Deep git/repository understanding beyond basic status + log.
|
||||
- Automatic command execution (even "safe" suggestions).
|
||||
- Structured editing / patch generation.
|
||||
- Multi-turn conversation state.
|
||||
## Explicitly Out of Scope (Current and Near-Term)
|
||||
|
||||
- Full deep integration with the complete `phase-memory` profile/planner/graph system (current implementation uses a deliberate, user-visible local JSON store with contextual activation; deeper integration is planned future work per MemoryVision.md).
|
||||
- Deep llm-connect features beyond basic `execute_prompt` delegation (adaptive routing, cost dashboards, structured output schemas).
|
||||
- Deep semantic repository understanding or large-scale content analysis.
|
||||
- Automatic command execution (even "safe" suggestions) — explicit user confirmation remains mandatory for anything non-safe.
|
||||
- Hosted/team-shared shell sessions or autonomous background agents.
|
||||
- Cost tracking, token budgeting, or usage dashboards.
|
||||
- Team/shared memory or collaboration features.
|
||||
- Plugin system or domain-specific extensions.
|
||||
- Production PyPI publishing and fully automated release CI (a lightweight documented local process exists; automation is future work).
|
||||
|
||||
## Extension Points (Registered)
|
||||
|
||||
- `cya/llm/adapter.py` — `LLMAdapter` Protocol (the primary seam).
|
||||
- `cya/memory/__init__.py` — the four explicit ports (`remember_preference`, `recall_preferences`, `forget`, `export_memory`).
|
||||
- `cya/safety/risk.py` — the `_RULES` table and `classify()` function.
|
||||
- `cya/memory/__init__.py` — the explicit ports (with real implementation, contextual activation, and retrospection support).
|
||||
- `cya/safety/risk.py` — the `_RULES` table and `classify()` function (memory-aware).
|
||||
- `cya/context/collector.py` — collection functions and ignore policy.
|
||||
- `cya/orchestrator.py` — the main coordination entry point.
|
||||
- `cya/shell_session.py` — interactive shell runtime, slash commands, session artifacts, optional history, hub orientation, and weakness hints.
|
||||
- Packaging & distribution: `Makefile`, `pyproject.toml`, `docs/release-process.md`, and `MANIFEST.in` (first-class concern with registered future work).
|
||||
|
||||
## Success Criteria (Current Slice)
|
||||
## Success Criteria (Current State)
|
||||
|
||||
A new user can:
|
||||
- Clone the repo, run `pip install -e .`, and successfully use `cya` for 2–3 realistic tasks after reading only the README.
|
||||
- Understand exactly what context is being sent via `--explain-context`.
|
||||
A new user or contributor can:
|
||||
- Install the latest development code easily (`make dev-install` or direct git+) and use `cya` for realistic tasks after reading the README.
|
||||
- Understand exactly what context and memory influenced a response via `--explain-context`.
|
||||
- Trust that dangerous actions will never execute without explicit confirmation.
|
||||
- See a clear path for how real `llm-connect` and `phase-memory` will plug in later.
|
||||
- Start `cya shell` for multi-turn help, inspect `/explain`, export a redacted session, and opt in explicitly before shell history or session-turn memory is used.
|
||||
- Use `cya retrospect` to reflect on usage and set goals that influence future behavior.
|
||||
- Build and verify distribution packages locally.
|
||||
- See a clear path for how real `llm-connect`, deeper `phase-memory`, and future PyPI releases will integrate.
|
||||
|
||||
Sibling project owners can read the workplan + boundary documentation and know precisely where their packages integrate.
|
||||
Sibling project owners (llm-connect, phase-memory, State Hub) can read the workplans + boundary documentation and know precisely where their packages integrate.
|
||||
|
||||
---
|
||||
|
||||
**This SCOPE document reflects the state after the CYA-WP-0001 MVP slice.** It is intentionally narrower than the long-term vision in INTENT.md.
|
||||
**This SCOPE document reflects the state after CYA-WP-0007 (Interactive Shell Session) and CYA-WP-0008 (llm-connect Adapter Integration).**
|
||||
|
||||
It remains intentionally narrower than the long-term vision in INTENT.md and MemoryVision.md, but now incorporates significant advances in contextual memory activation, user-driven retrospection/optimization loops, and proper packaging & distribution capabilities.
|
||||
|
||||
See the individual workplans (especially CYA-WP-0003 and CYA-WP-0004) and `MemoryVision.md` for the intended direction of deeper `phase-memory` integration and future evolution.
|
||||
20
docs/CYA-WP-0006-profile-1-gap-checklist.md
Normal file
20
docs/CYA-WP-0006-profile-1-gap-checklist.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# CYA-WP-0006 — Profile 1 Gap Checklist
|
||||
|
||||
Audit of shipped Profile 1 spike (CYA-WP-0005-T05) vs MemoryVision Profile 1 acceptance criteria.
|
||||
Gates implementation tasks T02–T05.
|
||||
|
||||
| Gap | Spike state | Target (production) | Task |
|
||||
|-----|-------------|---------------------|------|
|
||||
| Capture UX | Single optional prompt; no preview | Guided 1–3 prompts (well / remember / avoid); preview + confirm; skip leaves no empty records | T02 |
|
||||
| Provenance | Plain key/value only | Session date + scope in reflection provenance | T02 |
|
||||
| Review / export | Manual JSON inspection | `cya memory reflections` lists/exports by scope | T03 |
|
||||
| Compaction | None | Near-duplicate detection; explicit merge/replace (no silent delete) | T03 |
|
||||
| Activation boost | Kind filter only | Reflections sorted ahead when recalled | T04 |
|
||||
| Explain surfacing | Item count + sample keys | Reflection count + truncated lesson text + provenance | T04 |
|
||||
| Response surfacing | Two reflections, 60-char truncate | Budget-capped "reflections influenced this" line | T04 |
|
||||
| Observability | `by_kind` in export | Reflection counts by scope in export | T05 |
|
||||
| Safety regression | Preference "never" tested | Reflections cannot downgrade destructive confirmation | T05 |
|
||||
| Tests | Single spike test | Paths for capture, compaction, surfacing, safety | T05 |
|
||||
| Docs | "spike" wording | README example session; MemoryVision "production" | T06 |
|
||||
|
||||
**Profile 0 invariants (must not regress):** user-controlled JSON, full provenance, memory adds caution only.
|
||||
16
docs/cya-config.example.toml
Normal file
16
docs/cya-config.example.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
# Example ~/.config/cya/config.toml — placeholders only; do not commit secrets.
|
||||
|
||||
[llm]
|
||||
adapter = "connect"
|
||||
backend = "openrouter"
|
||||
model = "anthropic/claude-sonnet-4"
|
||||
temperature = 0.3
|
||||
max_tokens = 2000
|
||||
api_key_env = "OPENROUTER_API_KEY"
|
||||
|
||||
# Optional interactive shell history context. Off by default.
|
||||
# Values are capped/redacted before they enter session context.
|
||||
[shell_history]
|
||||
enabled = false
|
||||
limit = 50
|
||||
# histfile = "~/.bash_history"
|
||||
172
docs/cya-interactive-shell-session-design.md
Normal file
172
docs/cya-interactive-shell-session-design.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# cya Interactive Shell Session Design
|
||||
|
||||
Status: implemented for CYA-WP-0007
|
||||
Date: 2026-06-23
|
||||
|
||||
## Scope Alignment
|
||||
|
||||
This design moves `cya shell` from the old deferred REPL note in SCOPE.md into
|
||||
owned product scope. It preserves the core INTENT principle: `cya` helps from
|
||||
inside the console while the user keeps control of context, history, memory,
|
||||
backend choice, and safety decisions.
|
||||
|
||||
Reviewed against:
|
||||
|
||||
- INTENT.md: console-native, user-controlled, explainable assistance.
|
||||
- SCOPE.md: `cya` owns UX, orchestration, safety, context, and documentation;
|
||||
State Hub remains non-runtime coordination.
|
||||
- MemoryVision.md: session turns are an episodic precursor with explicit
|
||||
`kind: session_turn`, user-triggered export, and opt-in persistence to memory.
|
||||
- AGENTS.md / State Hub boundary: hub reads are allowed for orientation;
|
||||
hub writes require explicit operator confirmation.
|
||||
|
||||
## REPL UX
|
||||
|
||||
Command:
|
||||
|
||||
```bash
|
||||
cya shell
|
||||
cya shell --offline --no-hub
|
||||
cya shell --with-history
|
||||
```
|
||||
|
||||
Prompt: `cya> `
|
||||
|
||||
The shell is intentionally still a helper alongside the user's shell. It never
|
||||
auto-executes commands. Natural-language turns are passed through the existing
|
||||
orchestrator path: context collection, memory recall, risk classification,
|
||||
mandatory confirmation for non-safe levels, adapter call, and rendering.
|
||||
|
||||
Slash commands:
|
||||
|
||||
- `/help` - show the shell command list.
|
||||
- `/exit` or `/quit` - close the shell session.
|
||||
- `/explain` - show the last turn's risk, context envelope, shell history
|
||||
provenance, hub summary, and weakness hints.
|
||||
- `/hub` - show loaded active workstreams, open tasks, inbox, and hub errors.
|
||||
- `/hub log "summary"` - post a State Hub progress note only after interactive
|
||||
confirmation.
|
||||
- `/inbox` - show unread messages.
|
||||
- `/inbox read <id>` - explicitly mark a message read.
|
||||
- `/export-session` - write a redacted JSON session summary.
|
||||
- `/learn` - capture Profile 1 reflections immediately.
|
||||
|
||||
EOF exits cleanly. Ctrl-C interrupts the current prompt and keeps the session
|
||||
alive. Non-TTY input is accepted for smoke/scripted use; end-session learning
|
||||
prompts are skipped unless the session is interactive.
|
||||
|
||||
## Session File Layout
|
||||
|
||||
Session artifacts are user-owned and local:
|
||||
|
||||
```text
|
||||
~/.config/cya/sessions/<session-id>.jsonl
|
||||
~/.config/cya/sessions/<session-id>-summary.json
|
||||
```
|
||||
|
||||
Session ids use `cya-YYYYMMDD-HHMMSS-<random>`.
|
||||
|
||||
JSONL event kinds:
|
||||
|
||||
- `session_start`: session metadata, cwd, loaded history context, compact hub
|
||||
orientation.
|
||||
- `session_turn`: one redacted turn with user text, assistant text, risk,
|
||||
cancellation/dry-run flags, weakness hints, and redaction count.
|
||||
- `session_export`: emitted when `/export-session` writes a summary file.
|
||||
- `session_end`: final turn count and timestamp.
|
||||
|
||||
`session_turn` is also registered as a memory kind. End-of-session memory writes
|
||||
are opt-in and store only redacted turn summaries through the existing memory
|
||||
port, not a new private store.
|
||||
|
||||
## Shell History Model
|
||||
|
||||
History is off by default.
|
||||
|
||||
Opt-in paths:
|
||||
|
||||
```bash
|
||||
cya shell --with-history
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```toml
|
||||
[shell_history]
|
||||
enabled = true
|
||||
limit = 50
|
||||
histfile = "~/.bash_history"
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Default hard cap: 50 lines.
|
||||
- Source: `$HISTFILE`, then `.bash_history`, `.zsh_history`, or fish history.
|
||||
- Per-line provenance: source path, line number, and `shell_history.histfile`.
|
||||
- Redaction before session context or persistence: API keys, tokens, passwords,
|
||||
common provider key forms, and secret-like env assignments.
|
||||
- `/explain` shows whether history was enabled, how many lines were included,
|
||||
redaction count, and provenance.
|
||||
- One-shot `cya "..."` remains history-free by default; no collector invariant
|
||||
changes are required for one-shot mode.
|
||||
|
||||
## State Hub Boundary
|
||||
|
||||
Session start reads:
|
||||
|
||||
- `.custodian-brief.md` if present.
|
||||
- `GET /workstreams/?topic_id=64418556-3206-457a-ba29-6884b5b12cf3&status=active`.
|
||||
- `GET /messages/?to_agent=can-you-assist&unread_only=true`.
|
||||
|
||||
Remote hub failures are non-fatal and are recorded in the session context.
|
||||
|
||||
Writes are explicit slash commands only:
|
||||
|
||||
- `/hub log "summary"` posts a progress note after confirmation.
|
||||
- `/inbox read <id>` marks one message read by explicit command.
|
||||
|
||||
No automatic State Hub writes occur on shell start, normal turns, export, or
|
||||
session close.
|
||||
|
||||
## Weakness Hints v1
|
||||
|
||||
Hints are deterministic informational panels. They never change the risk level,
|
||||
never skip confirmation, and never execute fixes.
|
||||
|
||||
Rules:
|
||||
|
||||
- `credential-routing`: secret-like topics without `warden route` guidance.
|
||||
- `ops-warden-secret-anti-pattern`: requests that imply ops-warden should vend
|
||||
API keys or passwords.
|
||||
- `remote-exec-review`: `curl`/`wget` piped to interpreters.
|
||||
- `repeated-destructive-intent`: repeated destructive or mass-edit turns.
|
||||
- `state-hub-alignment`: requested workplan id not visible in loaded hub
|
||||
summary, reminding the user that local workplan files are source of truth.
|
||||
|
||||
## End-of-Session Learning
|
||||
|
||||
On interactive close, the shell offers:
|
||||
|
||||
1. Profile 1 lesson capture using the same reflection helpers as
|
||||
`cya retrospect`.
|
||||
2. Optional redacted `session_turn` memory persistence.
|
||||
|
||||
Skipping either path writes no reflection or session-turn memory records.
|
||||
`/export-session` remains separate and always writes only a redacted JSON
|
||||
summary under the sessions directory.
|
||||
|
||||
## Gate Checklist
|
||||
|
||||
- T02 REPL skeleton: `cya shell` command, prompt loop, JSONL session file,
|
||||
`/help`, `/exit`, `/explain`, EOF/Ctrl-C handling.
|
||||
- T03 history: off by default, `--with-history`, config support, caps,
|
||||
redaction, provenance, tests.
|
||||
- T04 hub: brief + active workstreams + inbox, graceful offline behavior,
|
||||
`/hub`, `/hub log`, `/inbox`, explicit mark-read.
|
||||
- T05 orchestrator/safety: every turn uses `handle_request`; non-safe levels
|
||||
still require confirmation; `/explain` shows the full session context.
|
||||
- T06 learning/export: Profile 1 capture, `/export-session`, opt-in
|
||||
`session_turn` memory persistence.
|
||||
- T07 hints: at least three deterministic rules with tests; advisory only.
|
||||
- T08 docs/tests: README, AGENTS command reference, operator example, and
|
||||
pytest coverage.
|
||||
131
docs/cya-memory-activation-and-retrospection-concept.md
Normal file
131
docs/cya-memory-activation-and-retrospection-concept.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Conceptual Model: Contextual Memory Activation and Retrospection Loops (CYA-WP-0003 T01)
|
||||
|
||||
**Date:** 2026-05-27
|
||||
**Workplan:** CYA-WP-0003
|
||||
**Related:** INTENT.md, MemoryVision.md, history/2026-05-27-CYA-Intent-Scope-Gap-Analysis-Post-0002.md, CYA-WP-0002 results
|
||||
|
||||
## Guiding Principle
|
||||
|
||||
Memory in `cya` must be **proactively useful** and **user-steerable over time**.
|
||||
|
||||
Two complementary mechanisms achieve this:
|
||||
|
||||
1. **Contextual Activation** — Relevant memory is automatically surfaced based on the user's current working context (especially directory/project), without the user having to explicitly recall it every time.
|
||||
2. **Retrospection Loops** — Structured, recurring reflection sessions allow the user to review how memory was used, reflect on effectiveness, and explicitly set or refine goals, preferences, and "rules for future assistance."
|
||||
|
||||
Together they create a user-controlled continuous optimization loop: Use → Surface memory in context → Reflect → Evolve memory/goals → Better future context and behavior.
|
||||
|
||||
This directly addresses the remaining gaps after CYA-WP-0002: moving from passive storage to active, contextually-aware, longitudinally improving assistance.
|
||||
|
||||
## Alignment with INTENT.md and MemoryVision.md
|
||||
|
||||
**From INTENT.md:**
|
||||
- "Personalized Console Helper" that adapts to habits, preferred styles, repository conventions, recurring workflows, safety preferences.
|
||||
- `phase-memory` must support "project-specific memory", "distinguishing global, local, temporary, and contextual memory".
|
||||
- Core principle: user’s working memory belongs to the user.
|
||||
|
||||
**From MemoryVision.md:**
|
||||
- Primary relevant kinds: Project/Directory Memory, Workflow Recipes & Patterns, Learned Preferences & Conventions, Safety & Trust Signals.
|
||||
- `cya` is a consumer that requests activation of context packages.
|
||||
- Every response influenced by memory must explain **which** memory items and why.
|
||||
- Support for profile-driven behavior (what to stabilize, compact, etc.).
|
||||
|
||||
The proposed model is a direct, practical realization of these for the current and near-term phase (local JSON backing evolving toward full phase-memory).
|
||||
|
||||
## Model 1: Memory Activation
|
||||
|
||||
### Definition
|
||||
**Memory Activation** is the process by which `cya` automatically selects and injects relevant items from the user's memory store into the assistance context for a given request, based on contextual signals.
|
||||
|
||||
### Primary Activation Signal (MVP for this workplan)
|
||||
- **Current working directory + git root** (the `scope` already used in the T02 implementation).
|
||||
- When the user runs `cya` in a directory, the system preferentially activates items whose `scope` matches the cwd or any ancestor directory up to the git root.
|
||||
- Project identity (git remote or root path) can serve as a stronger key for "project memory".
|
||||
|
||||
### Activation Layers (in priority order)
|
||||
1. Explicitly user-pinned or high-priority items for the current scope.
|
||||
2. Recently used or high-confidence stabilized preferences for the project/directory.
|
||||
3. Workflow patterns and recipes previously approved/refined in this context.
|
||||
4. Safety & trust signals (e.g., "never auto-run" patterns remembered for this project).
|
||||
5. General (global) user preferences as fallback.
|
||||
|
||||
### Activation Metadata (for explainability)
|
||||
Every activated item must carry:
|
||||
- `provenance`: where it came from (retrospection, explicit remember, etc.)
|
||||
- `confidence` / stability phase hint
|
||||
- `reason_for_activation`: why it was selected for this context/request
|
||||
|
||||
This must be surfaced cleanly in `--explain-context` and (when relevant) in the final response.
|
||||
|
||||
### User Control
|
||||
- `--no-memory` or per-request opt-out.
|
||||
- Ability to say "always include X in this project" or "never activate Y here".
|
||||
- `cya memory export/inspect/forget` for the current scope.
|
||||
|
||||
## Model 2: Retrospection as a First-Class Interaction Pattern
|
||||
|
||||
### Definition
|
||||
A **Retrospection** is a deliberate, bounded session (user-initiated or lightly prompted) in which the user and `cya` jointly review recent interactions, memory usage, and outcomes, then explicitly capture reflection and forward-looking intent.
|
||||
|
||||
### Retrospection Flow (High-Level)
|
||||
1. Trigger: `cya retrospect` (or equivalent special prompt/mode).
|
||||
2. Review phase:
|
||||
- Show recent assistance cycles and which memory items were activated.
|
||||
- Surface outcomes (was the suggestion accepted? did it require extra clarification?).
|
||||
3. Reflection phase:
|
||||
- User answers guided questions: "What worked well?", "What should I have remembered?", "How do I want to be helped differently going forward?"
|
||||
4. Goal-setting / Intent capture:
|
||||
- New or updated preferences.
|
||||
- Interaction goals (e.g., "Prefer concise answers with one alternative", "Always warn about destructive commands even if I have approved the pattern before").
|
||||
- Explicit stabilization or forgetting decisions.
|
||||
5. Commit phase:
|
||||
- Outcomes are stored as first-class memory records (with `kind: "retrospection"` or "interaction_goal").
|
||||
- These receive preferential treatment in future activation (higher weight for the same scope).
|
||||
|
||||
### Retrospection Outputs as Higher-Order Memory
|
||||
- `kind: "interaction_goal"` or "retrospection_outcome"
|
||||
- Fields: goal_text, applies_to (scope, kinds, task_classes), priority, review_date
|
||||
- These can influence:
|
||||
- What gets activated.
|
||||
- Explanation depth and style passed in context to the LLM.
|
||||
- Safety tuning (e.g., "treat this pattern more conservatively in this project").
|
||||
|
||||
### Relationship to Activation
|
||||
Retrospection is one of the most powerful ways new memory enters the system in a high-quality, user-vetted form. Activated memory during normal use can trigger a lightweight retrospection prompt ("Would you like to capture this as a recurring preference?").
|
||||
|
||||
## Integration with Existing Ports and Future phase-memory
|
||||
|
||||
### Minimal Extensions Identified for T02
|
||||
Current ports (post-0002) already provide a good foundation via the `scope` and `profile` parameters.
|
||||
|
||||
Recommended small evolutions (to be detailed and implemented in T02):
|
||||
- Add support for a `kind` parameter / filter in `recall_preferences` (to boost or specifically request retrospection outcomes, interaction goals, etc.).
|
||||
- Add `activation_context` or richer hinting when calling recall (so the orchestrator can say "current cwd + recent task class").
|
||||
- Standardize a `retrospection` / `interaction_goal` kind in the stored records.
|
||||
- Add a lightweight `remember_retrospection_outcome(...)` helper (or just use `remember_preference` with special kind) for now.
|
||||
- Ensure `export_memory` can filter and pretty-print retrospection records.
|
||||
|
||||
Longer term (when wiring to full phase-memory):
|
||||
- Retrospection outcomes become natural input to profile execution and stabilization planners.
|
||||
- Activation becomes a proper request for a "context package" under user profile constraints.
|
||||
|
||||
### Explainability & Safety (Non-Negotiable)
|
||||
- Any memory-activated context (including from retrospection goals) must be explainable.
|
||||
- Retrospection-derived goals that would affect risky commands must still pass through the rule-based risk classifier (T03/T04 invariants preserved).
|
||||
- Users can always review, edit, or delete retrospection records.
|
||||
|
||||
## Proposed Design Artifacts to Produce in Later Tasks
|
||||
- Retrospection prompt templates / guided flow (T04).
|
||||
- Changes to orchestrator context assembly for automatic activation (T03).
|
||||
- Observability for "memory activated from retrospection goals" (T06).
|
||||
- User-facing examples in README (T07).
|
||||
|
||||
## Acceptance Criteria for T01 (Self-Check)
|
||||
|
||||
- [x] The two ideas (activation + retrospection) are clearly defined and shown to reinforce each other.
|
||||
- [x] Models are consistent with INTENT.md (personalized helper, project-specific memory, user control) and MemoryVision.md (kinds, activation, explainability, profile direction).
|
||||
- [x] Concrete, minimal extensions to the current port surface have been identified.
|
||||
- [x] Safety and explainability requirements are explicitly preserved.
|
||||
- [ ] (To be validated in review) The concepts feel natural in a terminal workflow.
|
||||
|
||||
This document serves as the primary output of T01. It can be referenced and evolved in subsequent tasks and eventually folded into MemoryVision.md or a dedicated user guide.
|
||||
43
docs/cya-shell-operator-session.md
Normal file
43
docs/cya-shell-operator-session.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Example Operator Session
|
||||
|
||||
This example uses offline mode and skips remote hub calls for a local smoke.
|
||||
In normal repo work, omit `--no-hub` so the shell reads State Hub orientation.
|
||||
|
||||
```text
|
||||
$ cya shell --offline --no-hub
|
||||
cya shell
|
||||
Session: cya-20260623-101500-1a2b3c4d
|
||||
Artifact: ~/.config/cya/sessions/cya-20260623-101500-1a2b3c4d.jsonl
|
||||
|
||||
State Hub remote orientation not available.
|
||||
History lines: 0
|
||||
|
||||
Type /help for commands, /exit to finish.
|
||||
cya> summarize what changed in this repo
|
||||
... standard orchestrator response ...
|
||||
cya> /explain
|
||||
... context envelope, memory, shell history status, hub summary, and hints ...
|
||||
cya> /hub
|
||||
... active workstreams or offline notes ...
|
||||
cya> /export-session
|
||||
Exported session summary: ~/.config/cya/sessions/cya-...-summary.json
|
||||
cya> /exit
|
||||
Session saved: ~/.config/cya/sessions/cya-...jsonl
|
||||
```
|
||||
|
||||
For history-aware continuity, opt in explicitly:
|
||||
|
||||
```bash
|
||||
cya shell --with-history
|
||||
```
|
||||
|
||||
The history context is capped, redacted, and shown in `/explain`. Secret values
|
||||
should still be routed through the approved custody path, not pasted into the
|
||||
session.
|
||||
|
||||
State Hub writes require a slash command and interactive confirmation:
|
||||
|
||||
```text
|
||||
cya> /hub log "Implemented CYA-WP-0007 shell skeleton and tests"
|
||||
Post progress note to http://127.0.0.1:8000? [y/N]: y
|
||||
```
|
||||
100
docs/llm-connect-integration.md
Normal file
100
docs/llm-connect-integration.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# llm-connect Integration (CYA-WP-0008)
|
||||
|
||||
## Mapping: cya ↔ llm-connect
|
||||
|
||||
| cya (`AssistanceRequest`) | llm-connect |
|
||||
|---------------------------|-------------|
|
||||
| `user_request` | User message body (after context framing) |
|
||||
| `context` (envelope + memory + `session_turns`) | Serialized into the prompt via `cya.llm.prompt.build_assistance_prompt` |
|
||||
| `hints` (`model`, `temperature`, `max_tokens`) | `RunConfig` fields for `execute_prompt` |
|
||||
| `AssistanceResponse.suggestion` | `LLMResponse.content` |
|
||||
| `AssistanceResponse.metadata` | `LLMResponse.model`, `usage`, `finish_reason` |
|
||||
|
||||
llm-connect owns provider clients (`create_adapter`), API key resolution, retries, and
|
||||
token usage. `cya` never imports vendor SDKs directly.
|
||||
|
||||
## Configuration
|
||||
|
||||
User config: `~/.config/cya/config.toml`
|
||||
|
||||
```toml
|
||||
[llm]
|
||||
adapter = "connect" # "connect" | "fake" (default: fake when absent)
|
||||
backend = "openrouter" # openrouter | openai | gemini | claude-code | mock
|
||||
model = "anthropic/claude-sonnet-4"
|
||||
temperature = 0.3
|
||||
max_tokens = 2000
|
||||
api_key_env = "OPENROUTER_API_KEY" # optional override
|
||||
# system_prompt = "..." # optional; uses cya default when omitted
|
||||
```
|
||||
|
||||
Optional project override: `.cya.toml` (same `[llm]` section; merged over user config).
|
||||
|
||||
Environment overrides:
|
||||
|
||||
| Variable | Purpose |
|
||||
|----------|---------|
|
||||
| `CYA_LLM_ADAPTER` | `connect` or `fake` |
|
||||
| `CYA_LLM_BACKEND` / `CYA_LLM_PROVIDER` | Provider name |
|
||||
| `CYA_LLM_MODEL` | Model id |
|
||||
|
||||
CLI: `cya --offline "..."` forces `FakeLLMAdapter`.
|
||||
|
||||
## Session context budget (multi-turn / `cya shell`)
|
||||
|
||||
Recent turns are passed in `AssistanceRequest.context["session_turns"]` as
|
||||
`{"user": "...", "assistant": "..."}` records.
|
||||
|
||||
Bounds (see `cya.config`):
|
||||
|
||||
- **Max turns:** 10
|
||||
- **Max characters:** 4000 (total across included turns)
|
||||
|
||||
Older or oversized history is dropped from the prompt automatically.
|
||||
|
||||
## Credential routing
|
||||
|
||||
Do **not** commit API keys. Before requesting secrets, route custody:
|
||||
|
||||
```bash
|
||||
warden route find "OpenRouter API key" --json
|
||||
warden route show <catalog-id> --json
|
||||
```
|
||||
|
||||
Typical ownership:
|
||||
|
||||
| Need | Owner | ops-warden executes? |
|
||||
|------|-------|----------------------|
|
||||
| `OPENROUTER_API_KEY` | OpenBao (`railiance-platform`) | No — route only |
|
||||
| `OPENAI_API_KEY` | OpenBao | No — route only |
|
||||
| `GEMINI_API_KEY` | OpenBao | No — route only |
|
||||
|
||||
llm-connect resolves keys via `resolve_api_key()` (explicit arg → env var → project key file).
|
||||
|
||||
## Adapter selection
|
||||
|
||||
`cya.llm.factory.get_adapter()` is the single factory for one-shot and shell paths:
|
||||
|
||||
1. `--offline` or `CYA_LLM_ADAPTER=fake` → `FakeLLMAdapter`
|
||||
2. `adapter = "connect"` in config/env → `LLMConnectAdapter` (graceful degrade on failure)
|
||||
3. Otherwise → `FakeLLMAdapter` (current default)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
make dev-install
|
||||
pip install -e ~/llm-connect # sibling checkout
|
||||
```
|
||||
|
||||
Optional extra group (placeholder for packaging): `pip install -e ".[llm]"`.
|
||||
|
||||
## Tests
|
||||
|
||||
- Default CI: `make test` — mocks llm-connect; no network.
|
||||
- Manual live check: `pytest -m llm_live` (requires configured API key).
|
||||
|
||||
## Known gaps
|
||||
|
||||
- Structured JSON output schema not enforced yet (free-form model text).
|
||||
- `claude-code` backend does not require an API key; other backends do.
|
||||
- Per-directory `.cya.toml` overrides user config but does not yet mirror llm-connect's full 7-layer resolution.
|
||||
87
docs/packaging-audit-and-requirements.md
Normal file
87
docs/packaging-audit-and-requirements.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Packaging Audit and Requirements (CYA-WP-0004 T01)
|
||||
|
||||
**Date:** 2026-05-27
|
||||
**Workplan:** CYA-WP-0004
|
||||
|
||||
## Current State Audit
|
||||
|
||||
### Packaging Configuration
|
||||
- **Build system**: `setuptools` (via `pyproject.toml`)
|
||||
- **Version**: Hardcoded as `0.1.0` in two places:
|
||||
- `pyproject.toml`
|
||||
- `src/cya/__init__.py`
|
||||
- **Entry point**: `cya = "cya.cli.main:run"` (correct)
|
||||
- **Package discovery**: `src` layout via `[tool.setuptools.packages.find]`
|
||||
- **Dependencies**: Minimal (`typer[standard]`, `rich`)
|
||||
- **No**:
|
||||
- `setuptools_scm` or dynamic versioning
|
||||
- `[project.optional-dependencies]`
|
||||
- `build` frontend usage
|
||||
- `MANIFEST.in` or equivalent include rules
|
||||
- `include-package-data` configuration
|
||||
- Development/test extras
|
||||
- `pyproject.toml` validation for `pip install git+...`
|
||||
|
||||
### Current Installation Experience
|
||||
- Only documented method: `pip install -e .` (editable)
|
||||
- `pip install git+https://...` is not reliable (version will be wrong, potential missing files)
|
||||
- No released packages exist yet
|
||||
|
||||
### Gaps Identified
|
||||
|
||||
**For "Install from Dev Head":**
|
||||
- No support for clean `pip install "git+https://github.com/worsch/can-you-assist.git@main"`
|
||||
- Version will be stuck at 0.1.0 even on latest code (bad for debugging)
|
||||
- Potential missing non-.py files in the sdist/wheel when installing from git
|
||||
- No documented one-liner or Makefile target for the primary user
|
||||
|
||||
**For "Release Packaging":**
|
||||
- No versioning strategy (how to bump, dev vs release versions)
|
||||
- No process to build clean, reproducible sdist + wheel
|
||||
- No `python -m build` usage
|
||||
- No release checklist or automation (even lightweight)
|
||||
- No way for others to `pip install can-you-assist` (or a future released name)
|
||||
|
||||
**General Technical Debt:**
|
||||
- Version is duplicated in two places (violates DRY)
|
||||
- No separation between build-time and runtime concerns
|
||||
- No packaging tests or verification step
|
||||
|
||||
## Requirements for CYA-WP-0004
|
||||
|
||||
### Must-Have for Dev-Head Install
|
||||
1. `pip install "git+https://github.com/worsch/can-you-assist.git"` (and `@main`, `@branch`) must produce a working `cya` with a dev-informative version.
|
||||
2. The primary user must have a simple, documented way to stay on latest code.
|
||||
3. Version must clearly indicate it is from development head (e.g., `0.2.0.devN+g<hash>` or similar).
|
||||
|
||||
### Must-Have for Release Packaging
|
||||
1. A clear, repeatable process to cut a new version.
|
||||
2. Ability to produce clean `sdist` + `wheel` using modern tools (`python -m build`).
|
||||
3. Version must be single-source-of-truth.
|
||||
4. Documentation in README + AGENTS.md for both installation methods.
|
||||
|
||||
### Nice-to-Have (in scope if low cost)
|
||||
- Makefile targets for common operations (`make dev-install`, `make dist`, `make check-dist`)
|
||||
- Basic CI step that verifies packaging works
|
||||
- Optional dependencies (e.g., `[dev]`, `[test]`)
|
||||
|
||||
## Versioning Strategy Recommendation (for T02)
|
||||
|
||||
**Recommended approach: Hybrid using `setuptools_scm`**
|
||||
|
||||
- For development/git installs: Automatic versions like `0.3.0.dev14+g3f2a1b7.d20260527`
|
||||
- For releases: Clean versions like `0.3.0`
|
||||
- Single source of truth in git tags + `pyproject.toml` configuration
|
||||
- Minimal changes to existing workflow
|
||||
|
||||
**Alternative (simpler but less powerful):** Keep static version + manual bumps + release checklist. Only use if `setuptools_scm` is deemed overkill.
|
||||
|
||||
**Decision needed in T02.**
|
||||
|
||||
## Next Steps
|
||||
|
||||
- T02 will decide and implement the versioning approach.
|
||||
- T03 will focus on making git-based installs reliable.
|
||||
- T04–T05 will focus on the release side.
|
||||
|
||||
This audit serves as the requirements baseline for the rest of the workplan.
|
||||
133
docs/phase-memory-optimization-suggestions.md
Normal file
133
docs/phase-memory-optimization-suggestions.md
Normal file
@@ -0,0 +1,133 @@
|
||||
# Optimization Suggestions & Missing Functionality for phase-memory
|
||||
|
||||
**Date:** 2026-05-28 (finalized in CYA-WP-0005 T04)
|
||||
**Source:** CYA-WP-0005 (Agentic Memory Profiles) + persisted research `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md`
|
||||
**Audience:** phase-memory / markitect owners (sister repo)
|
||||
**Purpose:** Actionable, prioritized feedback from the cya consumer so that Profiles 0–3 (and the self-improving loops they enable) can be realized cleanly, safely, and with excellent user control / explainability.
|
||||
|
||||
**Updated after T03:** Cross-references added to the crisp profile definitions and Capability Matrix now living in MemoryVision.md. The 9 categories below remain the concrete interface asks needed to enable the three variations.
|
||||
|
||||
## Context (Why This Feedback Exists)
|
||||
|
||||
`can-you-assist` (`cya`) is a **consumer** of the phase-memory profile-driven memory layer. After CYA-WP-0002/0003 we have a high-quality local approximation (Profile 0) behind explicit ports that already uses `profile`, `kinds`, `activation_context`, retrospection outcomes as higher-order memory, provenance, and phase hints. This delivers real directory/project-bound activation and a `cya retrospect` continuous-optimization loop today.
|
||||
|
||||
To evolve to true agentic self-improving behavior we need **three progressive profiles** (defined in the research doc and CYA-WP-0005):
|
||||
|
||||
- **Profile 1 (Reflexion-style verbal)**: Store and preferentially activate natural-language self-reflections / lessons (builds directly on existing retrospection kinds and `remember_retrospection_outcome`).
|
||||
- **Profile 2 (Generative-Agents-style hierarchical)**: Episodic stream → LLM synthesis of abstractions (conventions, patterns) with citations → storage in stabilized phases + multi-factor retrieval.
|
||||
- **Profile 3 (Procedural / meta-policy)**: First-class evolvable "how I behave" rules/procedures; meta-reflection proposes changes under strong audit + safety guardrails.
|
||||
|
||||
**Profile 0 is already shipped** (local JSON + activation + retrospection). The seam (4 ports + kinds + activation_context + provenance) is deliberately stable. The suggestions below are the concrete extensions we need from phase-memory to make Profiles 1–3 natural, safe, and explainable without cya owning storage or lifecycle policy.
|
||||
|
||||
All suggestions respect cya invariants:
|
||||
- Memory may only **increase** caution (rule-based RiskClassifier in cya is primary; memory feeds it but never relaxes it).
|
||||
- Everything is user-inspectable, exportable, and veto-able.
|
||||
- Full provenance for every item (raw or synthesized).
|
||||
- Dry-run / proposal semantics for any memory evolution.
|
||||
- cya never bypasses explicit user confirmation for non-SAFE actions.
|
||||
|
||||
## Prioritized Suggestions (9 Categories)
|
||||
|
||||
See the "Profiles 1–3: Definitions and cya Integration Plans" section + Capability Matrix in MemoryVision.md for the detailed intent and cya mappings that these interface asks are designed to enable.
|
||||
|
||||
### 1. Refined Port Signatures & Activation Context (Must-have for Profile 1, baseline for 2/3)
|
||||
- Make `activation_context: dict[str, Any]` (cwd, git_root, task_class, recent_kinds, token_budget_hint) a first-class, documented parameter on `recall_preferences` / context-package requests (cya already prototypes this locally).
|
||||
- Support `kinds: list[str]` filter + boost natively (cya has `KIND_PREFERENCE | RETROSPECTION | INTERACTION_GOAL`; will add `REFLECTION`, `SYNTHESIZED_CONVENTION`, `PROCEDURAL_RULE`, etc.).
|
||||
- Return structure from recall/export must include: `items[]` with per-item provenance + phase + citations (for synthesized), `provenance[]` array, `dry_run_plan` (when relevant), `profile`, `phase_summary`.
|
||||
- Add a distinct `remember_event` / `ingest_retrospection_outcome` (or keep thin wrapper) so planners receive high-quality, typed input from cya's guided reflection sessions.
|
||||
|
||||
**Why**: Enables the activation boost and kind-based preferential treatment that 0003 already proved valuable, and that all three profiles rely on.
|
||||
|
||||
### 2. Synthesis / Reflection Planner Hooks (Required for Profile 2, nice for 1)
|
||||
- Expose (or allow cya to request) a "run_reflection_pass(profile, scope, recent_memories, budget)" that returns proposed abstractions with citations, confidence, and suggested phase (fluid/stabilized).
|
||||
- Dry-run first: every synthesis returns a proposal object (before any write) that cya can present to the user for review/edit/approve ("phase-memory proposes 4 stabilized conventions from your last 12 sessions — accept?").
|
||||
- Recursive / hierarchical synthesis support (reflections on reflections) with citation chains.
|
||||
|
||||
**Why**: Directly implements the Generative-Agents reflection module and the "retrospection outputs as higher-order memory" model from 0003.
|
||||
|
||||
### 3. First-Class Procedural / Meta-Policy Support (Required for Profile 3)
|
||||
- Dedicated collection or `kind` namespace for procedural items (`procedural_rule`, `meta_policy`, `explanation_strategy`, `safety_tuning`).
|
||||
- Policy-evolution planner: input = recent outcomes + current procedural set + safety incidents; output = structured patch proposal + safety-impact analysis ("this change tightens risk posture on destructive commands").
|
||||
- Guardrail primitives that the PolicyGateway can enforce: "procedural changes may not relax risk classification without explicit user + operator override."
|
||||
|
||||
**Why**: Highest-leverage self-improvement layer (the assistant gets better at *being* an assistant). Highest risk if uncontrolled — needs the strongest dry-run + veto UX.
|
||||
|
||||
### 4. Activation & Multi-Factor Retrieval (Strong for 1 & 2, essential for 3)
|
||||
- Configurable composite scoring for retrieval: recency (decay), importance (LLM-inferred or user-pinned), relevance (to current activation_context or query), phase_weight, kind_boost.
|
||||
- Profile can declare defaults ("for terminal assistance in Rust projects, weight retrospection + procedural at 2.0").
|
||||
- cya can request "context package for a console turn, max 4k tokens, prioritize X kinds, under profile Y" and receive a planned package + explanation.
|
||||
|
||||
**Why**: Replaces cya's current simple boost logic with something phase-memory can optimize consistently across consumers.
|
||||
|
||||
### 5. Lifecycle, Phase Transitions & Compaction (Baseline for all profiles)
|
||||
- Explicit phase-transition proposals (fluid → stabilized → rigid) with human-readable diffs and citation back to source memories.
|
||||
- Compaction / eviction planner with dry-run + rationale ("these 17 near-duplicate reflections can be merged into 3; confidence 0.85").
|
||||
- TTL, review_date, last_used on items; cya can surface "this convention has not been reviewed in 90 days — re-validate in retrospect?"
|
||||
|
||||
**Why**: Realizes the ephemeral/fluid/stabilized/rigid model in MemoryVision and prevents unbounded growth while keeping user in control.
|
||||
|
||||
### 6. Explainability & Audit (Non-negotiable for all profiles)
|
||||
- Every memory item (raw, synthesized, or procedural) carries machine-readable + human-readable provenance: source memories, reflection prompt id or planner id, timestamps, author (user vs system), last review.
|
||||
- Structured "memory influence" objects that cya can render directly in `--explain-context` and final responses without fragile parsing.
|
||||
- Full export of a profile/scope includes the procedural rule set, recent synthesis activity, and token budget accounting by phase.
|
||||
|
||||
**Why**: Matches cya's core promise ("I can see exactly what it knows and why it used it") and is required for user trust when the system starts synthesizing and evolving its own behavior.
|
||||
|
||||
### 7. Safety & Policy Integration (Critical for 2 & 3)
|
||||
- Memory evolution operations declare safety impact: none / tightens / potentially relaxes.
|
||||
- Hook or query so cya's rule-based RiskClassifier can consult memory policy / procedural rules before activation (or to inject forced-confirmation signals).
|
||||
- "Memory evolution veto mode" (per-profile or global): all proposals require explicit approval; no auto-stabilization or auto-procedural changes.
|
||||
- Rollback / versioning for the procedural layer itself (user can say "revert to the rules from 2026-05-20").
|
||||
|
||||
**Why**: Preserves the hard invariant that memory never weakens safety. Procedural evolution is powerful but must never become a covert policy bypass.
|
||||
|
||||
### 8. Observability & Introspection (Strongly desired for 1–3)
|
||||
- Profile/scope introspection endpoint that returns: active procedural rules (with provenance), recent synthesis activity + citations, token usage by phase/kind, items nearing review or compaction.
|
||||
- Event log query so cya can answer "show me the memory events that caused this preference to be activated."
|
||||
- Cost / token / LLM-call accounting surfaced per recall or synthesis request (helps users understand the price of richer memory).
|
||||
|
||||
**Why**: Makes the self-improving loop transparent and debuggable from the terminal.
|
||||
|
||||
### 9. Retrospection / Continuous Optimization Interop (Direct enabler for 1 + foundation for 2/3)
|
||||
- Standard event schema or ingestion path for "retrospection session completed" (with captured goals, interaction_goals, reflections, explicit stabilization decisions) that phase-memory planners consume natively.
|
||||
- cya can tag a retrospection outcome as "high-importance for stabilization" or "candidate for procedural rule extraction."
|
||||
- Planners can propose "based on 3 recent retrospections in this project, I recommend promoting these 2 patterns to stabilized + adding 1 procedural rule — review?"
|
||||
|
||||
**Why**: `cya retrospect` (0003) is already the highest-quality way new, user-vetted memory enters the system. phase-memory should treat it as privileged input rather than generic events.
|
||||
|
||||
## Handoff Notes for phase-memory / markitect
|
||||
|
||||
- **Reference artifacts** (please read in this order):
|
||||
1. MemoryVision.md (especially the 0002 T01 integration contract with refined port signatures, phases, primary kinds, profile-driven behavior examples, safety/explainability requirements).
|
||||
2. `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` (full research + the three profile definitions + cya-specific mappings).
|
||||
3. `docs/cya-memory-activation-and-retrospection-concept.md` (0003 T01 — the activation layers + retrospection-as-higher-order-memory model that Profiles 1–3 build on).
|
||||
4. CYA-WP-0005 (this workplan) — tasks T01–T04 are the direct consumers of the feedback.
|
||||
5. Current cya port implementation: `src/cya/memory/__init__.py` (Profile 0; the signatures and return shapes we want to keep stable or evolve in place).
|
||||
|
||||
- **Coordination channel**: State Hub (topic 64418556-3206-457a-ba29-6884b5b12cf3 for can-you-assist / capabilities domain). Workstream for CYA-WP-0005 will be registered. Progress events and decisions will be posted there. Direct messages or shared docs also fine.
|
||||
|
||||
- **Prioritization guidance**:
|
||||
- Items in categories 1, 6, 7 are **foundational** — they protect user control, explainability, and safety even for Profile 0 today.
|
||||
- Categories 2, 4, 5, 9 enable Profile 1 + 2 with high leverage.
|
||||
- Category 3 + strong guardrails in 7 are the big unlock (and biggest risk) for Profile 3.
|
||||
- Observability (8) makes the whole story usable and debuggable.
|
||||
|
||||
- **Non-requests (for clarity)**: We are not asking phase-memory to own the cya CLI, the rule-based risk classifier, or terminal-specific prompting. We want the memory operating layer (profiles, planners, phases, graph/event store, policy) to be excellent so cya can be a thin, safe, highly-explainable consumer.
|
||||
|
||||
## Success Criteria (from cya perspective)
|
||||
|
||||
phase-memory + these suggestions are successful when a cya user can say:
|
||||
- "I ran `cya retrospect`, captured three lessons, and now the assistant reliably surfaces the right one in this project — and I can see exactly why."
|
||||
- "phase-memory proposed stabilizing four patterns from my last month of work; I reviewed the citations and accepted two. The assistant now behaves differently and I know why."
|
||||
- "I defined a procedural rule for how I like Rust error handling explained. The assistant follows it, and when it suggested changing the rule it showed me the safety impact and asked for approval."
|
||||
- "I exported my memory for project X, saw the procedural layer and the synthesis history, and deleted two items. The assistant respected it immediately."
|
||||
|
||||
These map directly to the long-term success criteria already in MemoryVision.md.
|
||||
|
||||
---
|
||||
|
||||
**Document status**: Living feedback artifact. Will be updated in CYA-WP-0005 T04 (polish + extraction) and as we learn more during profile implementation spikes. Please treat the numbered categories above as the actionable input for planning and interface design in phase-memory.
|
||||
|
||||
**Contact / next step**: After CYA-WP-0005 T04/T08 complete (workplan registered + suggestions doc finalized), the cya side will open a State Hub coordination message or workstream handoff with this doc + the research + MemoryVision contract attached.
|
||||
|
||||
Thank you — the profile model and self-improving loops are only as good as the memory operating system they run on. We are excited to collaborate on making phase-memory the excellent, user-controlled foundation for the whole ecosystem.
|
||||
117
docs/release-process.md
Normal file
117
docs/release-process.md
Normal file
@@ -0,0 +1,117 @@
|
||||
# Release Process (Lightweight)
|
||||
|
||||
**Goal**: Cut a new release of `can-you-assist` with low risk of mistakes and produce clean distribution artifacts.
|
||||
|
||||
This process is intentionally manual and lightweight. It can be automated later (CI + PyPI publishing).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- You have write access to the repository.
|
||||
- You have run `make dev-install` (or equivalent) so all dev tools are available.
|
||||
- You are on a clean `main` branch with all changes committed and pushed.
|
||||
|
||||
## Step-by-Step Release Process
|
||||
|
||||
### 1. Decide on the new version
|
||||
|
||||
Use semantic versioning:
|
||||
- `MAJOR.MINOR.PATCH` (e.g., `0.2.0`, `0.3.1`, `1.0.0`)
|
||||
|
||||
Check the current version:
|
||||
|
||||
```bash
|
||||
make version
|
||||
```
|
||||
|
||||
Decide the next version and note it (e.g., `0.2.0`).
|
||||
|
||||
### 2. Update version metadata (if not using git tags for releases)
|
||||
|
||||
> **Note**: With `setuptools_scm` (T02), the version is primarily driven by git tags for releases.
|
||||
> For a clean release, we will create a git tag. The version in `pyproject.toml` can stay dynamic.
|
||||
|
||||
If you want an explicit version bump before tagging (optional but sometimes useful for clarity):
|
||||
|
||||
- The version is derived from the upcoming git tag. You do **not** need to edit files for the release version itself.
|
||||
|
||||
### 3. Run the full test suite
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
All tests must pass.
|
||||
|
||||
### 4. Build clean distribution packages
|
||||
|
||||
```bash
|
||||
make clean
|
||||
make dist
|
||||
```
|
||||
|
||||
This produces artifacts in `dist/`:
|
||||
- `can_you_assist-<version>.tar.gz` (sdist)
|
||||
- `can_you_assist-<version>-py3-none-any.whl` (wheel)
|
||||
|
||||
### 5. (Optional but recommended) Smoke-test the built wheel
|
||||
|
||||
```bash
|
||||
python3 -m venv /tmp/verify-release
|
||||
/tmp/verify-release/bin/pip install dist/can_you_assist-*.whl
|
||||
/tmp/verify-release/bin/cya --version
|
||||
/tmp/verify-release/bin/cya --help
|
||||
rm -rf /tmp/verify-release
|
||||
```
|
||||
|
||||
The `cya` command must work and report the correct release version.
|
||||
|
||||
### 6. Commit any final release-related changes (if any)
|
||||
|
||||
Normally there should be none after T02 (versioning is dynamic via tags).
|
||||
|
||||
### 7. Create and push the release tag
|
||||
|
||||
```bash
|
||||
git tag -a v0.2.0 -m "Release v0.2.0"
|
||||
git push origin v0.2.0
|
||||
```
|
||||
|
||||
This tag will be picked up by `setuptools_scm` on the next build and will produce the clean version `0.2.0`.
|
||||
|
||||
### 8. (Future) Upload to PyPI
|
||||
|
||||
Once we have a PyPI publication workflow:
|
||||
|
||||
```bash
|
||||
python -m twine upload dist/*
|
||||
```
|
||||
|
||||
For now, the `dist/` artifacts can be shared manually or attached to a GitHub release.
|
||||
|
||||
## Makefile Targets Useful for Releases
|
||||
|
||||
```bash
|
||||
make clean # Remove build artifacts
|
||||
make dist # Build sdist + wheel (recommended)
|
||||
make version # Show current version
|
||||
make test # Run tests
|
||||
```
|
||||
|
||||
You can add more targets later (e.g., `make release-prep` that combines clean + test + dist).
|
||||
|
||||
## Safety Notes
|
||||
|
||||
- Never push a tag until you have verified the build and tests.
|
||||
- Release tags should be annotated (`-a`).
|
||||
- After a release, you can continue development on `main` — the next dev versions will automatically become `0.2.0.devN+...` thanks to `setuptools_scm`.
|
||||
|
||||
## Future Improvements (Out of Scope for This Slice)
|
||||
|
||||
- Automated release via GitHub Actions on tag push
|
||||
- Automatic PyPI upload on release
|
||||
- Changelog generation from commits
|
||||
- Signed releases
|
||||
|
||||
---
|
||||
|
||||
This process is intentionally simple so a single maintainer can execute it reliably with low cognitive load.
|
||||
134
history/2026-05-27-CYA-Intent-Scope-Gap-Analysis-Post-0002.md
Normal file
134
history/2026-05-27-CYA-Intent-Scope-Gap-Analysis-Post-0002.md
Normal file
@@ -0,0 +1,134 @@
|
||||
# Gap Analysis: INTENT.md vs SCOPE.md (Post CYA-WP-0002 Memory Integration)
|
||||
|
||||
**Date:** 2026-05-27
|
||||
**Repo:** can-you-assist
|
||||
**Workplan:** CYA-WP-0002 (completed)
|
||||
**Previous Analysis:** 2026-05-26 (Post 0001)
|
||||
**Author:** Grok
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Since the May 26 analysis (conducted immediately after CYA-WP-0001), the team has executed and completed **CYA-WP-0002 — Memory Integration Roadmap**.
|
||||
|
||||
This work directly targeted the largest identified gap from the previous analysis: **memory, preferences, and longitudinal user-controlled adaptation**.
|
||||
|
||||
### Key Change Since Last Analysis
|
||||
- **Memory** moved from "strictly minimal no-op ports" (T05 of 0001) to a **real, persisting, user-controlled implementation** with:
|
||||
- JSON-backed storage under `~/.config/cya/memory/` (explicitly user-inspectable and editable)
|
||||
- Full wiring into the orchestrator and `--explain-context`
|
||||
- Memory signals feeding the rule-based safety layer (T04)
|
||||
- Dedicated test coverage (T05)
|
||||
- Documentation and examples (T06)
|
||||
- The explicit port seam defined in T01/T05 remains the long-term integration point for full `phase-memory`.
|
||||
|
||||
**Overall Assessment**: The memory gap has been meaningfully closed for a first production-quality slice. The product now delivers on a core piece of the original INTENT that was previously missing. Other gaps remain.
|
||||
|
||||
## Strong Alignments (Updated)
|
||||
|
||||
| Area | INTENT.md Position | Current Reality (Post-0002) | Assessment |
|
||||
|-----------------------------------|--------------------------------------------------|-----------------------------------------------------------|---------------------|
|
||||
| Console-native experience | Foundational | Excellent | Strong match |
|
||||
| Safety & mandatory confirmation | Important | Core product behavior + memory-aware (T04) | Exceeded |
|
||||
| Explainability & transparency | Strong requirement | Very strong (provenance in memory + context) | Strong |
|
||||
| Backend agnosticism | Must use llm-connect seam | Clean LLMAdapter Protocol | Excellent |
|
||||
| User-controlled memory | Central principle | Real implementation now exists (T02), user-visible JSON | Major improvement |
|
||||
| Clear boundaries | cya / llm-connect / phase-memory separation | Still clearly documented | Good |
|
||||
|
||||
## Key Gaps (Post-0002)
|
||||
|
||||
### 1. Memory: From Largest Gap to "First Slice Delivered"
|
||||
|
||||
**Previous Status (May 26)**: The biggest divergence. Only no-op ports existed. No real accumulation of value across sessions.
|
||||
|
||||
**Current Status (May 27)**:
|
||||
- Real persisting memory for preferences and workflow patterns is implemented and working.
|
||||
- Users can `remember` things scoped to directories/projects and have them recalled later.
|
||||
- Memory is explainable and feeds safety.
|
||||
- Backing store is deliberately simple and user-owned (JSON files).
|
||||
|
||||
**Remaining Sub-Gaps** (intentional per the 0002 roadmap):
|
||||
- Still a local JSON implementation, not yet wired to the full `phase-memory` profile/planner/graph system described in MemoryVision.md and phase-memory's architecture.
|
||||
- No support yet for richer memory *kinds* (conversation history as first-class fluid memory, project knowledge graphs, etc.).
|
||||
- No profile-driven lifecycle, compaction, or stabilization yet (these are owned by phase-memory).
|
||||
|
||||
**Assessment**: Large positive movement. The "no memory at all" problem is solved for practical use. The next deepening is now a proper integration exercise rather than a foundational build.
|
||||
|
||||
### 2. Depth of Local Context Understanding
|
||||
|
||||
**INTENT.md** envisions rich assistance with code repositories, notes, project structures, and conventions.
|
||||
|
||||
**Current State**:
|
||||
- Context collector remains intentionally shallow (top-level cwd entries + basic git status).
|
||||
- No recursive understanding, no semantic awareness of code or notes.
|
||||
- Memory helps with *user-declared* project conventions, but the system does not autonomously discover deep structure.
|
||||
|
||||
**Gap Type**: Still medium-to-large. The tool is stronger for "I have a preference or pattern I want remembered" than for "understand this complex codebase for me."
|
||||
|
||||
### 3. One-Shot vs Longitudinal / Adaptive Value
|
||||
|
||||
**INTENT.md**:
|
||||
- Strong vision of the assistant becoming more useful over time through memory of habits, conventions, aliases, recurring workflows, preferred explanation styles, safety tolerance, etc.
|
||||
- "Personalized console helper."
|
||||
|
||||
**Current Reality (Post-0002)**:
|
||||
- We now have the basic mechanism for this (remember/recall scoped preferences).
|
||||
- Early adaptation is possible today (users can teach `cya` their preferences).
|
||||
- However, higher-order adaptation (automatically learning patterns from accepted suggestions, building project "personalities", long-term user models) is not yet present.
|
||||
- Most usage is still fundamentally one-shot + explicit memory seeding.
|
||||
|
||||
**Gap Type**: Reduced but still significant. The foundation for longitudinal value now exists; rich automatic adaptation is future work.
|
||||
|
||||
### 4. Safety Implementation (Continued Positive Gap)
|
||||
|
||||
Memory signals are now considered in risk classification (T04) while preserving the hard "never auto-execute" rule. This strengthens the original intent.
|
||||
|
||||
**Assessment**: Remains a strength.
|
||||
|
||||
### 5. Documentation and Perceived Scope (Meta Gap)
|
||||
|
||||
**Observation**:
|
||||
- The current [SCOPE.md](/home/worsch/can-you-assist/SCOPE.md) still describes the world as it existed after CYA-WP-0001.
|
||||
- It continues to say memory ports are "strictly minimal no-op" and lists durable memory as out of scope.
|
||||
- This creates a documentation gap between what the code and README now deliver and what the official Scope document claims.
|
||||
|
||||
This is a maintenance issue rather than a product gap, but it is noticeable.
|
||||
|
||||
## Summary Table (Updated)
|
||||
|
||||
| Gap Area | Severity (Post-0001) | Severity (Post-0002) | Trend | Notes |
|
||||
|---------------------------------|----------------------|----------------------|------------|-------|
|
||||
| Memory & Adaptation | Large | Medium | Much better | Real prefs + wiring delivered; full phase-memory integration pending |
|
||||
| Depth of Context Understanding | Medium-Large | Medium-Large | Unchanged | Still shallow collector |
|
||||
| Longitudinal / Automatic Adaptation | Large | Medium | Improved | Foundation exists via explicit memory |
|
||||
| Safety & Explainability | Positive | Positive | Stable | Further strengthened |
|
||||
| Documentation vs Reality | Small | Medium | Worsened | SCOPE.md is now stale |
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Update SCOPE.md** (high priority)
|
||||
- Reflect post-0002 reality: real user-controlled memory now exists.
|
||||
- Distinguish between "current implementation" (local JSON + ports) and "long-term target" (deep phase-memory integration).
|
||||
- Move durable memory out of "explicitly out of scope."
|
||||
|
||||
2. **Continue the Memory Roadmap**
|
||||
- The natural next work would be deeper integration with `phase-memory` (once it exposes stable high-level APIs for preferences/project context) or richer memory kinds beyond simple key-value prefs.
|
||||
|
||||
3. **Consider a "Context Depth" Slice**
|
||||
- If richer repository and note understanding becomes important, a dedicated workplan focused on improving the collector + adding project memory conventions would address the second-largest remaining gap.
|
||||
|
||||
4. **Keep the Explicit Seam Discipline**
|
||||
- The four ports + MemoryVision contract remain the correct boundary. Future work should continue to flow through them rather than building parallel memory systems inside cya.
|
||||
|
||||
## Conclusion
|
||||
|
||||
CYA-WP-0002 successfully closed the most glaring hole between INTENT and delivered reality. The product now has a credible story for user-controlled memory and early personalization that was completely absent after the MVP.
|
||||
|
||||
The remaining gaps are more about *depth and richness* of context/memory rather than the complete absence of foundational capabilities. This represents healthy progress aligned with the original vision, while maintaining the disciplined, boundary-respecting approach established in 0001.
|
||||
|
||||
---
|
||||
|
||||
**Related Documents**
|
||||
- Previous analysis: `history/2026-05-26-CYA-Intent-Scope-Gap-Analysis.md`
|
||||
- Memory vision: `MemoryVision.md`
|
||||
- Completed memory workplan: `workplans/CYA-WP-0002-memory-integration-roadmap.md`
|
||||
- Current (stale) scope: `SCOPE.md` (recommended for update)
|
||||
250
history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md
Normal file
250
history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Agentic Memory & Self-Improving Loops: Research Synthesis and Three Variations for can-you-assist
|
||||
|
||||
**Date:** 2026-05-28
|
||||
**Context:** Post CYA-WP-0004 (packaging), during planning for memory profile evolution (CYA-WP-0005)
|
||||
**Related:** MemoryVision.md, docs/cya-memory-activation-and-retrospection-concept.md, CYA-WP-0002/0003, history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md, phase-memory (sister repo)
|
||||
**Author:** Grok (research + synthesis for cya)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Deep research into agentic memory architectures for LLM agents reveals mature patterns for **closed self-improving loops**: Trial/Interaction → Evaluation/Feedback (success/failure signals, user outcomes) → Reflection/Synthesis/Critique (verbal or structured) → Memory Update/Consolidation (store abstractions, rules, or critiques) → Improved future behavior via retrieval/activation.
|
||||
|
||||
Key exemplars:
|
||||
- **Reflexion (Shinn et al., NeurIPS 2023)**: Verbal reinforcement via stored self-reflections. Dramatic gains with minimal infra.
|
||||
- **Generative Agents (Park et al., SIGGRAPH 2023)**: Memory stream + LLM-powered reflection module synthesizing abstractions + multi-factor scored retrieval.
|
||||
- **Procedural / Meta-policy evolution** (LangMem, A-Mem, LEGOMem patterns): Agents dynamically rewrite their own high-level rules, procedures, or system instructions based on experience.
|
||||
|
||||
These map cleanly to cya's existing foundation (post-0003: real JSON memory with `kinds`, `activation_context`, retrospection outcomes as higher-order memory, directory/project scoping, provenance, safety integration, `cya retrospect` guided loop) and the phase-memory vision (phases: ephemeral/fluid/stabilized/rigid; profile-driven planners; explicit ports).
|
||||
|
||||
**Recommendation for cya**: Establish current (post-0003 local JSON + activation + retrospection) as explicit **Profile 0** baseline. Then implement **Profiles 1–3** incrementally, each adding one dimension of agentic self-improvement while preserving non-negotiables: user control/inspectability, explainability (provenance + `--explain-context`), rule-based safety (memory never downgrades risk or bypasses confirmation), and the cya/phase-memory boundary (cya consumes; phase-memory owns storage/lifecycle/planners).
|
||||
|
||||
This document persists the research and directly feeds CYA-WP-0005 (profile definitions + phase-memory interface feedback).
|
||||
|
||||
## Research Sources & Core Patterns
|
||||
|
||||
### Reflexion (Shinn et al. 2023)
|
||||
- Core idea: After each trial (e.g., code gen + test feedback, or assistant suggestion + user outcome), the agent generates a **natural-language self-reflection** ("what went wrong / what should I remember for next time?") using the LLM itself.
|
||||
- Store these reflections in an **episodic memory buffer** (list of (trajectory, reflection, outcome)).
|
||||
- On next similar task: prepend the most relevant past reflections + trajectories to the prompt.
|
||||
- Feedback sources flexible: unit tests, environment rewards, human critique, or (in cya) explicit user "accept / revise / reject" + retrospection.
|
||||
- Results (paper): +11–34% absolute gains on HumanEval / HotpotQA / AlfWorld with GPT-4 / Claude-1; works with open models too.
|
||||
- Key properties: **lightweight** (no fine-tuning, no embeddings required for MVP), **highly explainable** (reflections are human-readable English), **verbal reinforcement learning**.
|
||||
- Limitations: Can accumulate noise if reflections are low-quality; needs good relevance filtering or recency/importance scoring.
|
||||
|
||||
**Primary citation**: Shinn et al., "Reflexion: Language Agents with Verbal Reinforcement Learning", https://arxiv.org/abs/2303.11366 (NeurIPS 2023). Additional analysis: Lilian Weng "LLM Powered Autonomous Agents" (https://lilianweng.github.io/posts/2023-06-23-agent/), LangChain Reflexion tutorial.
|
||||
|
||||
### Generative Agents (Park et al. 2023)
|
||||
- **Memory stream**: All experiences stored as natural-language "memory objects" (timestamped observations, actions, thoughts).
|
||||
- **Reflection module** (periodic, often at night or low-load): LLM is prompted to generate questions about recent memories, then synthesize **high-level abstractions** (e.g., "I prefer concise git status output and always want alternatives listed") with citations back to source memories. Recursive: reflections can themselves be reflected upon → hierarchy.
|
||||
- **Retrieval**: For a new situation, score every memory by a composite:
|
||||
- Recency (exponential decay)
|
||||
- Importance (LLM infers 1–10 saliency for the agent)
|
||||
- Relevance (embedding cosine similarity to current context/query)
|
||||
- Retrieved memories + top reflections fed into planning / action.
|
||||
- Used to drive believable agents in a Sims-like sandbox (25 agents, 2 weeks simulated).
|
||||
- Strengths: Produces **rich, human-interpretable** higher-order knowledge; natural hierarchy aligns with phase-memory's fluid→stabilized→rigid phases.
|
||||
- Cost: More LLM calls for synthesis; needs embedding store or smart sampling for scale.
|
||||
|
||||
**Primary citation**: Park et al., "Generative Agents: Interactive Simulacra of Human Behavior", https://arxiv.org/abs/2304.03442 (SIGGRAPH 2023). Excellent overview in Lilian Weng post above.
|
||||
|
||||
### Procedural Memory & Meta-Policy Evolution (LangMem, A-Mem, LEGOMem, MemGPT patterns)
|
||||
- **Procedural memory**: Not "what happened" or "what I prefer", but **"how I should behave / what procedure to follow"**.
|
||||
- LangMem (LangChain): Three stores — episodic (events), semantic (facts), **procedural** (evolving instructions / system prompts / rules the agent rewrites for itself).
|
||||
- Agents observe outcomes, run "meta-reflection", and propose patches to their own high-level policy or few-shot examples.
|
||||
- **A-Mem (Agentic Memory)**: Dynamic graph where memories are nodes with auto-generated keywords/tags/context; agents autonomously evolve the graph structure and connections over time. No fixed schema.
|
||||
- **LEGOMem**: Procedural memory specifically for reusable workflows / "recipes" (e.g., "when debugging a failing test in this project: run X, inspect Y, propose minimal patch").
|
||||
- **MemGPT**: OS-inspired virtual context management — "main context" (RAM), recall (disk), archival (cold store); paging + function calls to manage what is in the LLM window. Hierarchical + procedural control.
|
||||
- Common thread: The agent treats its own **operating policy / instructions / strategies** as first-class mutable memory that improves via experience.
|
||||
- Highest upside for self-improvement (the system literally gets "smarter at being an assistant" rather than just knowing more facts).
|
||||
- Highest risk: Uncontrolled policy drift, safety regressions, user surprise ("why did it start doing X differently?").
|
||||
|
||||
**Key references**:
|
||||
- LangMem: https://blog.langchain.dev/langmem-building-production-ready-agents-with-long-term-memory/ and docs.
|
||||
- A-Mem: https://arxiv.org/abs/2409.16166 (autonomous memory evolution).
|
||||
- MemGPT: https://arxiv.org/abs/2310.08560.
|
||||
- Lilian Weng survey (covers procedural aspects).
|
||||
|
||||
### Cross-Cutting Architectural Patterns
|
||||
- **Episodic stream** (time-stamped records) scored by recency/importance/relevance + activation context (directory, project, task class).
|
||||
- **Reflection / synthesis** as a distinct, often offline or user-triggered step (natural fit for `cya retrospect`).
|
||||
- **Hierarchical / phased storage**: raw events → summaries → abstractions → rigid core (aligns perfectly with phase-memory phases).
|
||||
- **Procedural layer** on top: meta-rules that govern retrieval, synthesis thresholds, safety posture, explanation style, etc.
|
||||
- **Provenance + audit everywhere**: every synthesized item cites sources; every activation explains why.
|
||||
- **Dry-run / proposal semantics** for any memory evolution (user veto before commit).
|
||||
- **Safety firewall**: memory-derived signals can only **increase** caution (cya RiskClassifier invariant).
|
||||
|
||||
## Three Variations for cya + phase-memory
|
||||
|
||||
These are ordered by increasing agentic power and implementation cost. All preserve cya's core contract (user-controlled, explainable, safety-first, explicit seam to phase-memory).
|
||||
|
||||
### Variation 1: Reflexion-Style Verbal Self-Improvement Loop (Profile 1 candidate)
|
||||
**Core loop**:
|
||||
1. Normal assistance (memory activated via current 0003 activation_context + kinds).
|
||||
2. Outcome capture: user accepts/revises/rejects, or runs `cya retrospect`.
|
||||
3. Verbal reflection: LLM (or guided prompt) produces 1–3 concise "lessons" in natural language, e.g. "In this Rust project the user always wants `cargo clippy` before suggesting fixes; remember to surface that early."
|
||||
4. Store via `remember_retrospection_outcome` (or new `remember_reflection`) with `kind="reflection"` or `kind="verbal_lesson"`.
|
||||
5. Future recall: boost `kinds=["reflection", "retrospection", "interaction_goal"]` + activation_context match; prepend top-N to context envelope (high salience).
|
||||
6. Explainability: surface "3 verbal reflections from prior sessions in this project influenced this suggestion" + the actual text in `--explain-context`.
|
||||
|
||||
**cya mapping (builds directly on 0003)**:
|
||||
- Existing `KIND_RETROSPECTION` + `remember_retrospection_outcome` + `cya retrospect` flow is ~80% of the infrastructure.
|
||||
- Add a lightweight "capture lesson" step at end of retrospect (or auto after notable interactions).
|
||||
- New `kind` values + filter support already in ports.
|
||||
- Preferential activation for reflection kinds (small change to current boost logic).
|
||||
- Safety: reflections that touch risky patterns still route through RiskClassifier.
|
||||
|
||||
**Pros**: Lowest infra cost, maximum explainability (plain English), immediate value, fits terminal workflow perfectly.
|
||||
**Cons**: Reflections can be noisy or contradictory without synthesis (addressed in Var 2).
|
||||
**Phase-memory fit**: Minimal new requirements — just good support for `kind` filtering + provenance on recall. Later: a "reflection planner" that can suggest compaction of duplicate lessons.
|
||||
|
||||
**See also**: Shinn paper, current `docs/cya-memory-activation-and-retrospection-concept.md` (retrospection outputs as higher-order memory).
|
||||
|
||||
### Variation 2: Generative-Agents-Style Hierarchical Memory Stream + Synthesis (Profile 2 candidate)
|
||||
**Core loop**:
|
||||
1. Episodic capture: every assistance turn, outcome, retrospection, explicit remember → structured memory record (timestamp, kind, scope, activation_context, raw text or structured payload, provenance).
|
||||
2. Periodic synthesis (user-triggered via `cya retrospect --synthesize`, or background/low-load, or phase-memory planner): LLM clusters recent fluid memories for the scope/profile, generates abstractions (preferences, project conventions, recurring workflows, "in this repo we always..."), with citations.
|
||||
3. Store synthesized items with higher phase hint ("stabilized") and new kinds (e.g. `KIND_SYNTHESIZED_CONVENTION`, `KIND_PROJECT_PATTERN`).
|
||||
4. Retrieval/activation: multi-factor scoring (recency + explicit importance + relevance to current cwd/git/task_class + profile match) + the existing activation_context boost.
|
||||
5. Hierarchy: raw retrospection → synthesized convention → (later) higher-order "this user values X across all projects".
|
||||
6. Compaction: phase-memory (or cya helper) can propose merging/evicting with dry-run + user review.
|
||||
|
||||
**cya mapping**:
|
||||
- Current local JSON can serve as the episodic stream (already has ts, scope, kind, profile, value).
|
||||
- `export_memory` + `recall_preferences(..., activation_context=...)` already return provenance.
|
||||
- Orchestrator already injects activated memory into ContextEnvelope.
|
||||
- Extend retrospect flow to offer "synthesize patterns from recent sessions" as an explicit user choice.
|
||||
- Add simple importance scoring (LLM call or heuristic) on remember.
|
||||
- Wire synthesized items back through the same recall path (they just have different kinds/phases).
|
||||
|
||||
**Pros**: Richer longitudinal value, directly realizes MemoryVision "Project / Directory Memory" + "Workflow Recipes", natural fit for phase-memory's phases and planners.
|
||||
**Cons**: More LLM calls (synthesis cost); requires good citation tracking to keep explainability.
|
||||
**Phase-memory fit**: Strong alignment. phase-memory should expose:
|
||||
- Synthesis / reflection planner entrypoint (or allow cya to request "run reflection pass for this profile/scope").
|
||||
- Structured memory objects with citation/provenance fields.
|
||||
- Phase transition proposals (fluid → stabilized) with dry-run.
|
||||
- Multi-factor retrieval API that cya can parameterize (recency weight, importance weight, relevance query).
|
||||
|
||||
**See also**: Park paper, MemoryVision phases + "profile-driven behavior" section, 0003 activation layers.
|
||||
|
||||
### Variation 3: Procedural / Meta-Policy Evolution (Profile 3 candidate — aspirational)
|
||||
**Core loop**:
|
||||
1. Base behavior defined by a small set of **first-class procedural memory items** (kinds: `procedural_rule`, `meta_policy`, `explanation_strategy`, `safety_tuning`).
|
||||
- Example: "When user is in a Rust project with recent compilation errors, always surface `cargo check` output early and propose minimal patches before broad refactors."
|
||||
2. Meta-reflection (triggered after retrospection, or on explicit "improve my assistant rules"): LLM reviews recent outcomes + current procedural rules + safety incidents, proposes **patches or new rules**.
|
||||
3. Proposal + audit: phase-memory (or cya) presents the diff ("+1 procedural rule, -0, changed confidence on 2") in human review form; user can edit/approve/veto.
|
||||
4. On approval: the new rule is stored with high phase/stability and becomes part of future activation / prompt construction / risk hints.
|
||||
5. Guardrails: all procedural changes are **additive or tightening only** by default (never relax safety without explicit user action); every rule carries provenance + last-review date; cya RiskClassifier treats procedural memory as a strong "force confirmation" signal source.
|
||||
|
||||
**cya mapping**:
|
||||
- New dedicated kinds + a `remember_procedural_rule` helper (thin wrapper).
|
||||
- A new `cya improve-rules` or extension to `retrospect` that runs the meta-reflection.
|
||||
- Orchestrator / safety layer reads procedural items with highest priority for injection (system prompt augmentation or risk context).
|
||||
- Export/inspect must make procedural layer very prominent ("these 7 rules govern how I behave for you in this project").
|
||||
- Strong integration with the existing rule-based RiskClassifier (procedural memory can only raise the assessed risk level).
|
||||
|
||||
**Pros**: Highest self-improvement leverage — the assistant literally gets better at *being an assistant* for this user.
|
||||
**Cons**: Highest risk of drift or unintended behavior; requires the strongest audit, veto, and rollback UX.
|
||||
**Phase-memory fit** (most demanding):
|
||||
- First-class **procedural memory** kind + dedicated planner (policy evolution planner).
|
||||
- Proposal / diff semantics for memory changes (dry-run + structured review objects).
|
||||
- Explicit hooks for "meta-reflection" passes that cya can invoke with bounded context.
|
||||
- Safety / policy gateway that can enforce "procedural changes may not weaken risk posture without user override".
|
||||
- Versioning / rollback for the procedural layer itself.
|
||||
- Profile can declare "aggressiveness of self-evolution" (conservative / balanced / bold) with corresponding planner behavior.
|
||||
|
||||
**See also**: LangMem procedural store, A-Mem autonomous evolution, LEGOMem workflow recipes, safety invariants in cya's classifier.py (T04 0002).
|
||||
|
||||
## Profile 0 Baseline (Current Post-0003 Reality)
|
||||
|
||||
Before defining 1–3, the workplan must explicitly document and stabilize **Profile 0** as the shipped baseline so future profiles have a clear "from here" point.
|
||||
|
||||
**Exact current implementation** (as of post-0003 + 0004):
|
||||
- Backing: user-controlled `~/.config/cya/memory/<scope>.json` (list of records). Inspectable, editable, deletable by user. No hidden state.
|
||||
- Ports (src/cya/memory/__init__.py):
|
||||
- `KIND_PREFERENCE`, `KIND_RETROSPECTION`, `KIND_INTERACTION_GOAL`
|
||||
- `remember_preference(key, value, scope="cwd", *, profile=None, ttl=None, kind=KIND_PREFERENCE)`
|
||||
- `recall_preferences(scope="cwd", task_class=None, *, kinds=None, profile=None, limit=50, activation_context=None)` → returns items + rich provenance + phase hint + note about local json
|
||||
- `forget(scope, keys=None, *, profile=None)`
|
||||
- `export_memory(scope, *, profile=None, kinds=None)` → includes `by_kind` counts, provenance_summary, phases list
|
||||
- `remember_retrospection_outcome(...)` convenience (sets kind automatically)
|
||||
- Activation (0003): simple but effective boost — items whose scope/profile match `activation_context` (populated by ContextEnvelope with cwd + git_root) are moved to front of results. Wired in orchestrator.py for every assistance request + `--explain-context` panel.
|
||||
- Retrospection (0003): `cya retrospect` subcommand (guided questions, review recent memory usage, capture goals → stored as first-class retrospection records with preferential future activation).
|
||||
- Safety integration (unchanged invariant): memory signals only append rationale or force confirmation in RiskClassifier; never downgrade risk level or bypass mandatory confirmation.
|
||||
- Observability: `--explain-context` surfaces activated memory items, provenance, phase, activation reason.
|
||||
- Explainability: every recall/export carries "source: cya-local-memory-json (T02+0003; activation + retrospection support)".
|
||||
- Limitations (intentional): no embeddings, no synthesis, no procedural layer, no phase transitions, local only (no graph/event store), no profile execution planner yet.
|
||||
|
||||
This is deliberately a **high-quality local approximation** that delivers real user value today (contextual activation + continuous optimization loop via retrospect) while keeping the seam (profile/kind/activation_context parameters) ready for replacement by full phase-memory.
|
||||
|
||||
**Profile 0 success criteria** (for 0005 T0x): The above is clearly documented in MemoryVision.md (add "What CYA-WP-0003 Delivered" + "Profile 0 Baseline" sections), tests cover kinds + activation_context + retrospection roundtrips, README/AGENTS mention the current loop, and the new workplan treats it as the stable starting point for 1–3.
|
||||
|
||||
## Recommendations & Next Steps (for CYA-WP-0005)
|
||||
|
||||
1. **Persist this research** (this document) and reference it from the workplan.
|
||||
2. Create CYA-WP-0005 with tasks for:
|
||||
- Formalize Profile 0 baseline (update MemoryVision, SCOPE, code comments, tests).
|
||||
- Define + implement Profile 1 (Reflexion verbal) as small delta on existing retrospect/kinds machinery.
|
||||
- Profile 2 skeleton (episodic capture + synthesis entrypoint + hierarchical kinds).
|
||||
- Profile 3 exploration spike (procedural kind + proposal UX, strong guardrails).
|
||||
- Standalone or section "Optimization Suggestions & Missing Functionality for phase-memory" (see below).
|
||||
- Update docs, examples, MemoryVision with profile matrix.
|
||||
- Register workplan, commit, sync via fix-consistency.
|
||||
3. **Do not** start full implementation of 1–3 until the workplan is reviewed and activated (per AGENTS convention).
|
||||
|
||||
## Optimization Suggestions & Missing Functionality for phase-memory (Sister Repo)
|
||||
|
||||
(This section is the direct input for the "provide optimization suggestions to the sister repo" part of the request. It will be extracted or expanded as a deliverable in 0005.)
|
||||
|
||||
**High-priority interface / contract improvements** (to make Profiles 1–3 natural):
|
||||
|
||||
1. **Refined / extended port signatures** (build on the CYA-WP-0002 T01 contract already in MemoryVision):
|
||||
- Add `activation_context: dict` (cwd, git_root, task_class, recent_kinds) as first-class param to recall (already prototyped in cya local).
|
||||
- Support `kinds: list[str]` filter/boost in recall (already in cya; make phase-memory native).
|
||||
- Return richer `provenance` + `dry_run_plan` + `phase` + `synthesis_citations` in recall/export results.
|
||||
- Add `remember_event(...)` or `ingest_retrospection_outcome(...)` as distinct from generic remember (for planner input).
|
||||
|
||||
2. **Synthesis / Reflection planner hooks**:
|
||||
- Expose (or allow cya to request) a "run_reflection_pass(profile, scope, recent_memories)" that returns proposed abstractions with citations and confidence.
|
||||
- Dry-run semantics for all synthesis proposals (cya can present "phase-memory proposes these 4 stabilized conventions — review?").
|
||||
|
||||
3. **First-class procedural / meta-policy support** (for Profile 3):
|
||||
- Dedicated `kind` values or a separate `procedural_rules` collection.
|
||||
- Policy evolution planner: input = recent outcomes + current rules; output = proposed patches + impact analysis (esp. safety impact).
|
||||
- Guardrail primitives: "this change may not relax risk posture" that the PolicyGateway can enforce.
|
||||
|
||||
4. **Activation & retrieval enhancements**:
|
||||
- Multi-factor scoring API (recency, importance, relevance, user_pinned, phase_weight) that cya can parameterize per request or per profile.
|
||||
- Profile-aware activation requests: cya says "give me a context package for a terminal assistance turn in this cwd under profile X, budget 4k tokens, prioritize retrospection + procedural kinds".
|
||||
|
||||
5. **Lifecycle & phase management**:
|
||||
- Explicit phase transition proposals (fluid → stabilized) with user-reviewable diffs.
|
||||
- Compaction / eviction planner with dry-run + rationale.
|
||||
- TTL + review_date on memory items (cya can surface "this convention has not been reviewed in 90 days").
|
||||
|
||||
6. **Explainability & audit**:
|
||||
- Every returned memory item (raw or synthesized) must carry machine + human readable provenance (source memories, reflection prompt id, timestamp, author).
|
||||
- Structured "memory influence" objects that cya can render cleanly in `--explain-context` and final output without custom parsing.
|
||||
|
||||
7. **Safety & policy integration**:
|
||||
- Memory evolution operations must be able to declare "safety impact: none / tightens / potentially relaxes".
|
||||
- Hook for cya's rule-based RiskClassifier to consult memory policy before activation (or to inject forced-confirmation signals from procedural rules).
|
||||
- User-level "memory evolution veto mode" (all proposals require explicit approval; no auto-stabilization).
|
||||
|
||||
8. **Observability & introspection**:
|
||||
- `export_memory` / profile introspection that includes "active procedural rules", "recent synthesis activity", "token budget usage by phase".
|
||||
- Event log query API so cya can show "memory events that led to this activated preference".
|
||||
|
||||
9. **Retrospection / continuous optimization interop**:
|
||||
- Standard event schema for "retrospection session completed" that phase-memory planners can consume directly.
|
||||
- Ability for cya to say "this retrospection outcome should be treated as high-importance for stabilization".
|
||||
|
||||
**Non-goals for near-term phase-memory feedback**: full embedding/semantic index details, voice, cross-user sharing, production scale numbers. Focus on the **profile + planner + procedural + dry-run proposal** surface that lets cya deliver the three self-improving variations safely and explainably.
|
||||
|
||||
These suggestions will be turned into a clean, reviewable artifact (standalone doc or PR-ready section) in CYA-WP-0005 and shared with the phase-memory / markitect owners via State Hub or direct coordination.
|
||||
|
||||
---
|
||||
|
||||
**End of persisted research document.**
|
||||
This artifact, together with the three gap analyses in history/, MemoryVision.md, and the activation concept doc, forms the knowledge base for CYA-WP-0005.
|
||||
|
||||
Next: Create the workplan that turns this research into executable tasks (profile 0 baseline + 1–3 + phase-memory feedback deliverable).
|
||||
131
history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md
Normal file
131
history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# Gap Analysis: INTENT.md vs SCOPE.md (Post CYA-WP-0004)
|
||||
|
||||
**Date:** 2026-05-28
|
||||
**Repo:** can-you-assist
|
||||
**Workplan:** CYA-WP-0004 (completed)
|
||||
**Previous Analysis:** 2026-05-27 (Post 0002)
|
||||
**Author:** Grok
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Since the May 27 analysis (post-0002), two major follow-on workplans have been completed:
|
||||
|
||||
- **CYA-WP-0003**: Contextual Memory Activation & Retrospection Loops — delivered directory/project-bound automatic memory activation and the `cya retrospect` guided reflection mechanism for continuous user-driven optimization.
|
||||
- **CYA-WP-0004**: Developer Installation from Git and Release Distribution Packaging — delivered reliable dev-head installation, dynamic versioning, clean distribution package building, a lightweight release process, and clear documentation for both dev and future released usage.
|
||||
|
||||
The largest previous gap (memory & longitudinal adaptation) has been substantially addressed in practice (though deeper `phase-memory` integration remains future work). Packaging/distribution has moved from a notable gap to a documented, owned capability.
|
||||
|
||||
**Overall Assessment**: Strong progress. The product is now significantly closer to the "Personalized Console Helper" vision in INTENT.md, with excellent safety, explainability, and user control. Remaining gaps are more about depth and future evolution than foundational missing pieces.
|
||||
|
||||
## Strong Alignments (Updated)
|
||||
|
||||
| Area | INTENT.md Position | Current Reality (Post-0004) | Assessment |
|
||||
|-----------------------------------|--------------------------------------------------|------------------------------------------------------------------|---------------------|
|
||||
| Console-native experience | Foundational | Excellent | Strong match |
|
||||
| Safety & mandatory confirmation | Important | Core product behavior + memory-aware | Exceeded |
|
||||
| Explainability & transparency | Strong requirement | Very strong (provenance for context + memory + retrospection) | Strong |
|
||||
| Backend agnosticism | Must use llm-connect seam | Clean LLMAdapter Protocol + Fake | Excellent |
|
||||
| User-controlled memory | Central principle | Real, contextually activated, retrospection-supported (local) | Major improvement |
|
||||
| Longitudinal adaptation | Personalized helper that improves over time | `cya retrospect` + automatic activation now provides a real loop | Significantly better|
|
||||
| Packaging & distribution | Not heavily emphasized | Dev-head install + release process now first-class | Major positive shift|
|
||||
| Clear boundaries | cya / llm-connect / phase-memory separation | Clearly documented | Good |
|
||||
|
||||
## Key Gaps (Post-0004)
|
||||
|
||||
### 1. Memory & Adaptation — Substantially Addressed, Deeper Integration Pending
|
||||
|
||||
**Previous Status (Post-0002)**: Real but mostly passive local JSON memory.
|
||||
|
||||
**Current Status (Post-0004)**:
|
||||
- Strong contextual activation based on directory/project (T03 of 0003).
|
||||
- `cya retrospect` provides a structured mechanism for reflection and goal setting (T04 of 0003).
|
||||
- Retrospection outcomes feed back into future behavior (continuous optimization loop).
|
||||
- Excellent explainability and user control.
|
||||
|
||||
**Remaining Sub-Gaps** (intentional):
|
||||
- Still a local JSON implementation. Full integration with `phase-memory`'s profile/planner/graph system (as described in MemoryVision.md) is planned future work.
|
||||
- Richer memory kinds (beyond preference + interaction goals) and automatic pattern learning are not yet present.
|
||||
|
||||
**Assessment**: Large positive movement. The "memory as passive store" problem has been meaningfully solved. The foundation for true longitudinal, user-steerable adaptation now exists.
|
||||
|
||||
### 2. Depth of Local Context Understanding
|
||||
|
||||
**INTENT.md** envisions rich assistance with code repositories, notes, project structures, and conventions.
|
||||
|
||||
**Current State**:
|
||||
- Context collector remains intentionally shallow (top-level entries + basic git).
|
||||
- Memory helps significantly with *user-declared* project conventions and patterns, which is a practical improvement.
|
||||
- No deep semantic understanding or large-scale analysis.
|
||||
|
||||
**Assessment**: Still a medium gap. 0003's activation + retrospection helps users bring their own context effectively, but the system itself does not deeply understand repositories or notes.
|
||||
|
||||
### 3. One-Shot vs Rich Longitudinal Value
|
||||
|
||||
**INTENT.md**: Strong vision of the assistant becoming more useful over time through memory of habits, conventions, and recurring workflows.
|
||||
|
||||
**Current Reality (Post-0004)**:
|
||||
- Users can now teach the system via normal use + explicit `cya retrospect` sessions.
|
||||
- Directory/project-bound activation makes memory feel proactive.
|
||||
- The retrospection loop provides a deliberate mechanism for continuous improvement.
|
||||
|
||||
**Assessment**: Significantly improved. The basic mechanism for longitudinal, user-controlled adaptation is now in place and usable. Rich automatic pattern learning remains future work.
|
||||
|
||||
### 4. Packaging & Distribution — Major Positive Shift
|
||||
|
||||
**Previous Status**: Effectively a gap (only editable install documented).
|
||||
|
||||
**Current Status (Post-0004)**:
|
||||
- Reliable dev-head installation from git (local or direct).
|
||||
- Dynamic versioning.
|
||||
- Clean, verifiable distribution package building.
|
||||
- Documented lightweight release process.
|
||||
- Clear documentation distinguishing dev vs future released usage.
|
||||
|
||||
**Remaining Sub-Gaps** (registered as debt):
|
||||
- Actual PyPI publishing workflow.
|
||||
- Automated releases via CI.
|
||||
- Package signing.
|
||||
- Multi-Python distribution testing as a CI requirement.
|
||||
|
||||
**Assessment**: Excellent progress. What was previously a notable weakness is now a documented strength with a clear path forward.
|
||||
|
||||
### 5. Safety & Explainability — Continued Strength
|
||||
|
||||
Remains a core strength. Memory activation and retrospection outcomes are required to flow through the rule-based risk classifier, and all memory influence is visible via `--explain-context`.
|
||||
|
||||
## Summary Table (Updated)
|
||||
|
||||
| Gap Area | Severity (Post-0002) | Severity (Post-0004) | Trend | Notes |
|
||||
|-----------------------------------|----------------------|----------------------|--------------------|-------|
|
||||
| Memory & Longitudinal Adaptation | Medium | Small-Medium | Much better | Strong activation + retrospection loop now exists |
|
||||
| Depth of Context Understanding | Medium-Large | Medium | Improved (via memory) | Collector still shallow; user memory helps |
|
||||
| Packaging & Distribution | Medium (gap) | Small | Major improvement | Now first-class with process + docs |
|
||||
| Safety & Explainability | Positive | Positive | Stable | Remains a core strength |
|
||||
| One-shot vs Rich Longitudinal | Medium | Small | Significantly better | Foundation for continuous optimization in place |
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Continue evolving the memory story** toward deeper `phase-memory` integration (as outlined in MemoryVision.md and the 0003 contract). This is the natural next major deepening.
|
||||
|
||||
2. **Consider a dedicated "Context Depth" exploration** if richer repository/note understanding becomes a priority (separate from the memory activation work).
|
||||
|
||||
3. **Move packaging forward** when ready:
|
||||
- Add `make check-dist` (or equivalent) as a required CI step.
|
||||
- Define and implement the PyPI publishing workflow.
|
||||
- Consider automated releases on annotated tags.
|
||||
|
||||
4. **Keep the explicit seam discipline** — the ports, activation model, and retrospection flow from 0003 remain excellent boundaries.
|
||||
|
||||
## Conclusion
|
||||
|
||||
CYA-WP-0003 and 0004 have together delivered substantial progress against the original INTENT.md vision. The product now has credible, usable mechanisms for contextual memory, user-driven continuous optimization, and proper packaging/distribution — areas that were previously either missing or significantly weaker.
|
||||
|
||||
The remaining gaps are largely about *depth and future evolution* rather than missing foundational capabilities. The project is in a much stronger position relative to its original intent than it was after 0002.
|
||||
|
||||
---
|
||||
|
||||
**Related Documents**
|
||||
- Previous analysis: `history/2026-05-27-CYA-Intent-Scope-Gap-Analysis-Post-0002.md`
|
||||
- Memory vision: `MemoryVision.md`
|
||||
- Recent workplans: `workplans/CYA-WP-0003-...md` and `CYA-WP-0004-...md`
|
||||
- Current SCOPE.md (updated as part of this assessment)
|
||||
129
history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0005.md
Normal file
129
history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0005.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# Gap Analysis: INTENT.md vs SCOPE.md (Post CYA-WP-0005)
|
||||
|
||||
**Date:** 2026-05-28
|
||||
**Repo:** can-you-assist
|
||||
**Workplan:** CYA-WP-0005 (completed)
|
||||
**Previous Analysis:** 2026-05-28 (Post 0004)
|
||||
**Author:** Grok
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Since the May 28 analysis (post-0004), the major workplan **CYA-WP-0005: Agentic Memory Profiles and phase-memory Interface Optimization** has been completed via ralph loop.
|
||||
|
||||
Key deliveries:
|
||||
- Formal **Profile 0 baseline** (post-0003 local JSON + kinds + activation_context + provenance + retrospection) explicitly documented as the stable foundation.
|
||||
- Complete profile model: detailed definitions, cya integration plans, and a Capability Matrix for **Profiles 1–3** (Reflexion verbal, hierarchical synthesis, procedural/meta-policy evolution).
|
||||
- First delivered self-improving capability: **minimal but working Profile 1** (Reflexion-style verbal reflections/lessons) spike — `remember_reflection()` + `KIND_REFLECTION`, optional "capture verbal lesson" step inside `cya retrospect`, preferential activation, and surfacing in responses / `--explain-context`.
|
||||
- Finalized the `docs/phase-memory-optimization-suggestions.md` handoff artifact for the sister repo.
|
||||
|
||||
**Overall Assessment**: Very strong progress on the most important remaining gap from INTENT.md — the "Personalized Console Helper" that improves over time through user-controlled memory. The memory & longitudinal adaptation gap has moved from "Small-Medium" to "Small". The project now has both the conceptual framework (profiles) and a concrete first implementation (Profile 1 verbal loop) that directly addresses INTENT's vision of longitudinal adaptation via user-steerable memory.
|
||||
|
||||
Remaining gaps are now primarily about *depth* (richer context understanding, deeper phase-memory integration for Profiles 2/3, procedural evolution) rather than missing foundational mechanisms.
|
||||
|
||||
## Strong Alignments (Updated)
|
||||
|
||||
| Area | INTENT.md Position | Current Reality (Post-0005) | Assessment |
|
||||
|-----------------------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------|-------------------------|
|
||||
| Console-native experience | Foundational | Excellent | Strong match |
|
||||
| Safety & mandatory confirmation | Important | Core behavior + memory-aware (reflections can force confirmation) | Exceeded |
|
||||
| Explainability & transparency | Strong requirement | Very strong (provenance everywhere, reflections surfaced explicitly, `--explain-context`) | Strong |
|
||||
| Backend agnosticism | Must use llm-connect seam | Clean LLMAdapter Protocol + Fake | Excellent |
|
||||
| User-controlled memory | Central principle | Real + now architected with explicit profiles (0 baseline + 1–3 roadmap) | Major improvement |
|
||||
| Longitudinal adaptation | Personalized helper that improves over time | `cya retrospect` + automatic activation + first working self-improvement loop (Profile 1) | Significantly better |
|
||||
| Packaging & distribution | Not heavily emphasized | Dev-head install + release process first-class | Major positive shift |
|
||||
| Clear boundaries | cya / llm-connect / phase-memory separation | Explicit ports + MemoryVision contract + phase-memory feedback doc | Good |
|
||||
| Profile-driven / self-improving behavior | Strong vision in "Personalized Console Helper" | Full model (T03) + first implementation (T05) | New major strength |
|
||||
|
||||
## Key Gaps (Post-0005)
|
||||
|
||||
### 1. Memory & Longitudinal Adaptation — Now a Clear Strength with Clear Next Steps
|
||||
|
||||
**Previous Status (Post-0004)**: Small-Medium. Good activation + retrospection loop, but still passive local JSON with limited self-improvement.
|
||||
|
||||
**Current Status (Post-0005)**:
|
||||
- Explicit **Profile 0** baseline documented as the shipped foundation.
|
||||
- Complete **Profiles 1–3** model with cya-specific mappings and Capability Matrix (T03).
|
||||
- **Profile 1** (Reflexion verbal) is not just designed — a working spike is shipped: verbal lessons can be captured during `cya retrospect`, stored with dedicated kind, preferentially activated, and surfaced to the user.
|
||||
- Strong explainability and user control preserved.
|
||||
|
||||
**Remaining Sub-Gaps** (now much smaller and more targeted):
|
||||
- Profile 1 is still a minimal spike (capture step + basic activation). Production hardening, better UX, and compaction of reflections would be natural follow-on work.
|
||||
- Profiles 2 (hierarchical synthesis) and 3 (procedural/meta-policy) remain design + research artifacts. No implementation yet (explicitly registered as debt in 0005).
|
||||
- Full wiring to `phase-memory` planners, graph store, and lifecycle rules is still future work (as intended).
|
||||
|
||||
**Assessment**: Large positive movement. The core INTENT vision of a user-controlled assistant that "improves over time" now has both architecture and a concrete first capability. This was the single largest gap for most of the project's history.
|
||||
|
||||
### 2. Depth of Local Context Understanding
|
||||
|
||||
**INTENT.md** envisions rich assistance with code repositories, notes, project structures, and conventions.
|
||||
|
||||
**Current State**:
|
||||
- Context collector remains intentionally shallow (top-level entries + basic git).
|
||||
- Memory (especially the new Profile 1 verbal lessons + user-declared retrospection goals) helps significantly with *user-taught* project conventions and patterns.
|
||||
- No automatic deep semantic understanding, embeddings, or large-scale repository analysis.
|
||||
|
||||
**Assessment**: Still a medium gap. The memory/profile work makes the *user* far more effective at bringing rich context into the assistant, but the system itself does not yet deeply understand repositories or notes on its own. This is consistent with original scope boundaries.
|
||||
|
||||
### 3. One-Shot vs Rich Longitudinal Value
|
||||
|
||||
**INTENT.md**: Strong vision of the assistant becoming more useful over time through memory of habits, conventions, and recurring workflows.
|
||||
|
||||
**Current Reality (Post-0005)**:
|
||||
- Users can teach the system via normal use + explicit `cya retrospect`.
|
||||
- Directory/project-bound activation + the new verbal reflection mechanism create a real, usable self-improvement loop.
|
||||
- Profile model provides a clear roadmap for richer future behavior (synthesis, procedural rules).
|
||||
|
||||
**Assessment**: Significantly improved. The basic mechanism for longitudinal, user-controlled adaptation now exists and is demonstrable with Profile 1. Richer automatic pattern learning and procedural evolution (Profiles 2/3) remain the natural next deepening.
|
||||
|
||||
### 4. Packaging & Distribution — Remains Strong
|
||||
|
||||
No regression. Still first-class with documented process.
|
||||
|
||||
### 5. Safety & Explainability — Continued Strength
|
||||
|
||||
Remains excellent. The new Profile 1 reflections flow through the same rule-based RiskClassifier and are fully visible in `--explain-context`.
|
||||
|
||||
## Summary Table (Updated)
|
||||
|
||||
| Gap Area | Severity (Post-0002) | Severity (Post-0004) | Severity (Post-0005) | Trend | Notes |
|
||||
|-----------------------------------|----------------------|----------------------|----------------------|------------------------|-------|
|
||||
| Memory & Longitudinal Adaptation | Medium | Small-Medium | Small | Major improvement | Profile model + first working self-improvement loop (Profile 1) |
|
||||
| Depth of Context Understanding | Medium-Large | Medium | Medium | Stable / slight improvement via user memory | Collector still shallow; user memory helps |
|
||||
| Packaging & Distribution | Medium (gap) | Small | Small | Stable | Remains first-class |
|
||||
| Safety & Explainability | Positive | Positive | Positive | Stable | Remains a core strength |
|
||||
| One-shot vs Rich Longitudinal | Medium | Small | Small | Stable / improved | Real loop exists; richer synthesis is future work |
|
||||
|
||||
## Recommendations
|
||||
|
||||
1. **Continue the memory evolution roadmap** as documented in CYA-WP-0005 and MemoryVision.md:
|
||||
- Production-harden Profile 1 (better capture UX, reflection compaction, more surfacing).
|
||||
- Begin exploration spikes for Profile 2 (synthesis) when ready.
|
||||
- Treat Profile 3 (procedural) as higher-risk / higher-reward and keep strong guardrails.
|
||||
|
||||
2. **Deepen phase-memory coordination** using the finalized `docs/phase-memory-optimization-suggestions.md`. The 9 categories (especially synthesis planner hooks, procedural support, and dry-run proposal semantics) are now well-scoped asks.
|
||||
|
||||
3. **Consider whether "Context Depth" becomes a first-class work item**. If richer automatic understanding of repositories/notes is desired, it will likely need its own focused slice (separate from the memory/profile work).
|
||||
|
||||
4. **Keep the seam discipline**. The explicit ports, activation model, and safety integration from 0002/0003/0005 remain excellent boundaries. Future work should extend rather than bypass them.
|
||||
|
||||
5. **Packaging follow-through** (unchanged from prior analysis): move the registered debt items (CI gate, PyPI workflow, automation) forward when capacity allows.
|
||||
|
||||
## Conclusion
|
||||
|
||||
CYA-WP-0005 has delivered the single most important conceptual and practical advance since the memory integration work of 0002/0003. The project now has:
|
||||
- An explicit, documented profile model that directly maps to INTENT's vision of longitudinal adaptation.
|
||||
- A working, user-visible first self-improving capability (Profile 1 verbal reflections).
|
||||
|
||||
The product is now substantially closer to the "Personalized Console Helper" described in INTENT.md than at any previous point. The remaining gaps are about *evolving and deepening* capabilities that now have clear architectural homes, rather than about missing foundational pieces.
|
||||
|
||||
This is a strong position from which to plan the next phase of work.
|
||||
|
||||
---
|
||||
|
||||
**Related Documents**
|
||||
- Previous analysis: `history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md`
|
||||
- Research foundation: `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md`
|
||||
- Memory vision & profile model: `MemoryVision.md`
|
||||
- Recent workplan: `workplans/CYA-WP-0005-agentic-memory-profiles-and-phase-memory-feedback.md`
|
||||
- Current SCOPE.md (updated as part of this assessment)
|
||||
- phase-memory feedback: `docs/phase-memory-optimization-suggestions.md`
|
||||
@@ -1,10 +1,10 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=64", "wheel"]
|
||||
requires = ["setuptools>=64", "wheel", "setuptools_scm>=8"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "can-you-assist"
|
||||
version = "0.1.0"
|
||||
dynamic = ["version"]
|
||||
description = "Console-native, backend-agnostic LLM assistant for practical local work from the shell. MVP slice."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
@@ -15,6 +15,20 @@ authors = [
|
||||
dependencies = [
|
||||
"typer[standard]>=0.12.0",
|
||||
"rich>=13.0.0",
|
||||
"tomli>=2.0.0; python_version<'3.11'",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"ruff>=0.4.0",
|
||||
"pytest>=8.0",
|
||||
"build>=1.0",
|
||||
]
|
||||
test = [
|
||||
"pytest>=8.0",
|
||||
]
|
||||
llm = [
|
||||
# Install llm-connect from a sibling checkout, e.g. pip install -e ~/llm-connect
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
@@ -25,6 +39,11 @@ where = ["src"]
|
||||
|
||||
[tool.setuptools]
|
||||
zip-safe = false
|
||||
include-package-data = true
|
||||
license-files = ["LICENSE"]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"*" = ["py.typed"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/worsch/can-you-assist"
|
||||
@@ -42,5 +61,11 @@ python_functions = ["test_*"]
|
||||
addopts = "-q --tb=short"
|
||||
markers = [
|
||||
"safety: core safety and risk classifier invariants (always run)",
|
||||
"llm_live: live llm-connect inference (requires API key; manual runs only)",
|
||||
]
|
||||
|
||||
[tool.setuptools_scm]
|
||||
write_to = "src/cya/_version.py"
|
||||
version_scheme = "guess-next-dev"
|
||||
local_scheme = "node-and-date"
|
||||
|
||||
|
||||
12
registry/README.md
Normal file
12
registry/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Capability Registry
|
||||
|
||||
Markdown-first capability index for federation and reuse planning.
|
||||
|
||||
## Authoring
|
||||
|
||||
1. Copy a capability entry template (see reuse-surface `templates/capability-entry.template.md`).
|
||||
2. Add the row to `indexes/capabilities.yaml`.
|
||||
3. Run `reuse-surface validate` from a checkout with the CLI installed.
|
||||
4. Merge to `main` and verify publish with `reuse-surface establish --publish-check`.
|
||||
|
||||
Federation contract: reuse-surface `docs/RegistryFederation.md`.
|
||||
0
registry/capabilities/.gitkeep
Normal file
0
registry/capabilities/.gitkeep
Normal file
120
registry/capabilities/capability.agents.cli-assistant.md
Normal file
120
registry/capabilities/capability.agents.cli-assistant.md
Normal file
@@ -0,0 +1,120 @@
|
||||
---
|
||||
id: capability.agents.cli-assistant
|
||||
name: Console-Native LLM Assistant (cya)
|
||||
summary: Console-native, backend-agnostic assistant CLI that expresses user intent in natural language
|
||||
and returns safe, explainable, context-aware help.
|
||||
owner: can-you-assist
|
||||
status: draft
|
||||
domain: agents
|
||||
tags:
|
||||
- cli
|
||||
- assistant
|
||||
- llm
|
||||
- safety
|
||||
maturity:
|
||||
discovery:
|
||||
current: D2
|
||||
target: D4
|
||||
confidence: medium
|
||||
rationale: README documents the MVP slice (CYA-WP-0001), the LLMAdapter seam, and the rule-based risk-classification
|
||||
safety layer; no separate INTENT/SCOPE file found.
|
||||
availability:
|
||||
current: A2
|
||||
target: A3
|
||||
confidence: medium
|
||||
rationale: Installable today via `pip install -e .`; ships a working CLI (`cya "..."`, `--explain-context`)
|
||||
with a FakeLLMAdapter default and optional real llm-connect backend.
|
||||
external_evidence:
|
||||
completeness:
|
||||
level: C1
|
||||
confidence: low
|
||||
basis: scope_vs_intent_and_consumer_expectations
|
||||
satisfied_expectations:
|
||||
- natural-language CLI entry point
|
||||
- context-explain mode
|
||||
- mandatory confirmation for destructive/privileged/mass-edit/network actions
|
||||
broken_expectations: []
|
||||
out_of_scope_expectations: []
|
||||
reliability:
|
||||
level: R0
|
||||
confidence: low
|
||||
basis: consumer_quality_signals
|
||||
known_reliability_risks:
|
||||
- MVP slice only; real llm-connect backend optional/not always configured
|
||||
discovery:
|
||||
intent: Let users express intent in natural language from the terminal and receive safe, explainable,
|
||||
context-aware help, without owning LLM inference or memory storage itself.
|
||||
includes:
|
||||
- CLI orchestration and UX
|
||||
- rule-based risk classification and confirmation gating
|
||||
- LLMAdapter seam to llm-connect
|
||||
excludes:
|
||||
- LLM inference (owned by llm-connect)
|
||||
- memory storage (owned by phase-memory)
|
||||
assumptions: []
|
||||
use_cases: []
|
||||
research_memos: []
|
||||
availability:
|
||||
current_level: A2
|
||||
target_level: A3
|
||||
current_artifacts:
|
||||
- '`cya` CLI package, pip-installable'
|
||||
target_artifacts: []
|
||||
consumption_modes:
|
||||
- cli
|
||||
relations:
|
||||
depends_on: []
|
||||
supports: []
|
||||
related_to: []
|
||||
evidence:
|
||||
documentation:
|
||||
- README.md
|
||||
tests:
|
||||
- tests/
|
||||
consumer_feedback: []
|
||||
bug_reports: []
|
||||
incidents: []
|
||||
consumer_guidance:
|
||||
recommended_for:
|
||||
- shell-based workflows wanting a safety-gated natural-language assistant
|
||||
not_recommended_for:
|
||||
- needs beyond the first MVP slice (CYA-WP-0001)
|
||||
known_limitations:
|
||||
- first narrow MVP slice; FakeLLMAdapter is the default, real backend is optional
|
||||
promotion_history: []
|
||||
---
|
||||
|
||||
# Console-Native LLM Assistant (cya)
|
||||
|
||||
## Overview
|
||||
|
||||
`cya` is the CLI surface for the capabilities domain: it owns orchestration, UX, and a safety layer, and talks to `llm-connect` only through a stable `LLMAdapter` boundary, keeping memory under `phase-memory`.
|
||||
|
||||
## Assessment notes
|
||||
|
||||
### Discovery
|
||||
|
||||
README documents the MVP slice (CYA-WP-0001), the LLMAdapter seam, and the rule-based risk-classification safety layer; no separate INTENT/SCOPE file found.
|
||||
|
||||
### Availability
|
||||
|
||||
Installable today via `pip install -e .`; ships a working CLI (`cya "..."`, `--explain-context`) with a FakeLLMAdapter default and optional real llm-connect backend.
|
||||
|
||||
### Completeness
|
||||
|
||||
First-pass honest assessment from the REUSE-WP-0017 coverage campaign
|
||||
(reuse-surface). No external consumer feedback exists yet; levels reflect
|
||||
scope-vs-intent documentation quality, not internal code quality.
|
||||
|
||||
### Reliability
|
||||
|
||||
No production consumer telemetry exists yet; reliability level is
|
||||
intentionally conservative pending REUSE-WP-0019 reuse-telemetry evidence.
|
||||
|
||||
## Promotion checklist
|
||||
|
||||
- [x] ID follows `capability.<domain>.<name>` pattern
|
||||
- [x] Maturity enums match `specs/CapabilityMaturityStandard.md`
|
||||
- [x] `external_evidence` is populated separately from `maturity`
|
||||
- [ ] Relations reference valid capability IDs (none yet)
|
||||
- [x] Index entry added in `registry/indexes/capabilities.yaml`
|
||||
20
registry/indexes/capabilities.yaml
Normal file
20
registry/indexes/capabilities.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
version: 1
|
||||
updated: '2026-07-06'
|
||||
domain: helix_forge
|
||||
capabilities:
|
||||
- id: capability.agents.cli-assistant
|
||||
name: Console-Native LLM Assistant (cya)
|
||||
summary: Console-native, backend-agnostic assistant CLI that expresses user intent in natural language
|
||||
and returns safe, explainable, context-aware help.
|
||||
vector: D2 / A2 / C1 / R0
|
||||
domain: agents
|
||||
status: draft
|
||||
owner: can-you-assist
|
||||
path: registry/capabilities/capability.agents.cli-assistant.md
|
||||
tags:
|
||||
- cli
|
||||
- assistant
|
||||
- llm
|
||||
- safety
|
||||
consumption_modes:
|
||||
- cli
|
||||
@@ -1,8 +1,12 @@
|
||||
"""can-you-assist (cya)
|
||||
|
||||
Console-native LLM assistant for practical local work (MVP scaffolding).
|
||||
Console-native LLM assistant for practical local work.
|
||||
|
||||
See workplans/CYA-WP-0001 for the full task breakdown and integration boundaries.
|
||||
"""
|
||||
|
||||
__version__ = "0.1.0"
|
||||
try:
|
||||
from ._version import __version__
|
||||
except ImportError:
|
||||
# Fallback for editable installs without setuptools_scm having run yet
|
||||
__version__ = "0.0.0+unknown"
|
||||
|
||||
@@ -11,6 +11,7 @@ This module will evolve in T06 (orchestrator) but the surface contract stays sta
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
import typer
|
||||
@@ -65,6 +66,11 @@ def main(
|
||||
"-n",
|
||||
help="Preview mode — do not perform any actions (stub in T01).",
|
||||
),
|
||||
offline: bool = typer.Option(
|
||||
False,
|
||||
"--offline",
|
||||
help="Use the deterministic FakeLLMAdapter (no llm-connect / no API keys).",
|
||||
),
|
||||
version: bool = typer.Option(
|
||||
None,
|
||||
"--version",
|
||||
@@ -105,11 +111,266 @@ def main(
|
||||
request,
|
||||
explain_context=explain_context,
|
||||
dry_run=dry_run,
|
||||
offline=offline,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
||||
@app.command("shell")
|
||||
def shell_command(
|
||||
with_history: bool = typer.Option(
|
||||
False,
|
||||
"--with-history",
|
||||
help="Opt in to capped, redacted shell-history context for this session.",
|
||||
),
|
||||
history_limit: int | None = typer.Option(
|
||||
None,
|
||||
"--history-limit",
|
||||
help="Maximum history lines to include, capped at the configured safe limit.",
|
||||
),
|
||||
offline: bool = typer.Option(
|
||||
False,
|
||||
"--offline",
|
||||
help="Use the deterministic FakeLLMAdapter for this shell session.",
|
||||
),
|
||||
hub_url: str = typer.Option(
|
||||
"http://127.0.0.1:8000",
|
||||
"--hub-url",
|
||||
help="State Hub base URL for read-only session orientation.",
|
||||
),
|
||||
no_hub: bool = typer.Option(
|
||||
False,
|
||||
"--no-hub",
|
||||
help="Skip State Hub HTTP orientation; still reads .custodian-brief.md if present.",
|
||||
),
|
||||
offer_session_lessons: bool = typer.Option(
|
||||
True,
|
||||
"--session-lessons/--no-session-lessons",
|
||||
help="Offer end-of-session Profile 1 reflection and session_turn memory capture.",
|
||||
),
|
||||
) -> None:
|
||||
"""Start an interactive, stateful cya shell session."""
|
||||
from cya.shell_session import run_shell
|
||||
|
||||
run_shell(
|
||||
with_history=with_history,
|
||||
history_limit=history_limit,
|
||||
offline=offline,
|
||||
hub_url=hub_url,
|
||||
no_hub=no_hub,
|
||||
offer_end_learning=offer_session_lessons,
|
||||
)
|
||||
|
||||
|
||||
memory_app = typer.Typer(
|
||||
help="Inspect and manage user-controlled memory (Profile 0 + Profile 1).",
|
||||
rich_markup_mode="rich",
|
||||
)
|
||||
app.add_typer(memory_app, name="memory")
|
||||
|
||||
|
||||
@memory_app.command("reflections")
|
||||
def memory_reflections(
|
||||
scope: str = typer.Option(
|
||||
".",
|
||||
"--scope",
|
||||
"-s",
|
||||
help="Directory or scope to list reflections for.",
|
||||
),
|
||||
export_json: bool = typer.Option(
|
||||
False,
|
||||
"--json",
|
||||
help="Export reflections as JSON (same as export_memory with kinds filter).",
|
||||
),
|
||||
) -> None:
|
||||
"""List or export verbal reflections for a scope (Profile 1)."""
|
||||
from cya.memory import export_memory, KIND_REFLECTION
|
||||
from cya.memory.reflections import list_reflections, reflection_export_stats
|
||||
|
||||
if export_json:
|
||||
data = export_memory(scope, kinds=[KIND_REFLECTION])
|
||||
console.print(json.dumps(data, indent=2, default=str))
|
||||
return
|
||||
|
||||
items = list_reflections(scope)
|
||||
stats = reflection_export_stats(scope)
|
||||
if not items:
|
||||
console.print(f"[yellow]No reflections in scope {scope!r}.[/yellow]")
|
||||
return
|
||||
|
||||
lines = []
|
||||
for item in items:
|
||||
prov = item.get("provenance") or {}
|
||||
date = prov.get("session_date", "?")
|
||||
lines.append(f"• [bold]{item.get('key')}[/bold] ({date}): {item.get('value')}")
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
"\n".join(lines),
|
||||
title=f"Reflections in {scope} ({stats['reflection_count']} total)",
|
||||
border_style="cyan",
|
||||
)
|
||||
)
|
||||
if stats.get("reflection_counts_by_scope"):
|
||||
console.print(
|
||||
f"[dim]By scope: {stats['reflection_counts_by_scope']}[/dim]"
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def retrospect(
|
||||
scope: str = typer.Option(
|
||||
".",
|
||||
"--scope",
|
||||
"-s",
|
||||
help="Directory or scope to run retrospection on (default: current directory).",
|
||||
),
|
||||
limit: int = typer.Option(
|
||||
8,
|
||||
"--limit",
|
||||
help="Number of recent memory items to review.",
|
||||
),
|
||||
) -> None:
|
||||
"""Start a guided retrospection session.
|
||||
|
||||
Review recent memory usage in this scope, reflect on what helped,
|
||||
and explicitly record new preferences or interaction goals.
|
||||
Outcomes are stored as first-class retrospection memory (T04 of 0003).
|
||||
"""
|
||||
from cya.orchestrator import run_retrospection
|
||||
|
||||
run_retrospection(scope=scope, limit=limit)
|
||||
|
||||
|
||||
def _dispatch_shell_argv(argv: list[str] | None = None) -> bool:
|
||||
"""Handle `cya shell ...` before Typer's root REQUEST argument sees it."""
|
||||
import argparse
|
||||
|
||||
argv = list(sys.argv if argv is None else argv)
|
||||
if len(argv) < 2 or argv[1] != "shell":
|
||||
return False
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f"{argv[0]} shell",
|
||||
description="Start an interactive, stateful cya shell session.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--with-history",
|
||||
action="store_true",
|
||||
help="Opt in to capped, redacted shell-history context for this session.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--history-limit",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Maximum history lines to include, capped at the configured safe limit.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--offline",
|
||||
action="store_true",
|
||||
help="Use the deterministic FakeLLMAdapter for this shell session.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hub-url",
|
||||
default="http://127.0.0.1:8000",
|
||||
help="State Hub base URL for read-only session orientation.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-hub",
|
||||
action="store_true",
|
||||
help="Skip State Hub HTTP orientation; still reads .custodian-brief.md if present.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--session-lessons",
|
||||
dest="offer_session_lessons",
|
||||
action="store_true",
|
||||
default=True,
|
||||
help="Offer end-of-session Profile 1 reflection and session_turn memory capture.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-session-lessons",
|
||||
dest="offer_session_lessons",
|
||||
action="store_false",
|
||||
help="Skip end-of-session learning prompts.",
|
||||
)
|
||||
args = parser.parse_args(argv[2:])
|
||||
|
||||
from cya.shell_session import run_shell
|
||||
|
||||
run_shell(
|
||||
with_history=args.with_history,
|
||||
history_limit=args.history_limit,
|
||||
offline=args.offline,
|
||||
hub_url=args.hub_url,
|
||||
no_hub=args.no_hub,
|
||||
offer_end_learning=args.offer_session_lessons,
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def _dispatch_memory_argv(argv: list[str] | None = None) -> bool:
|
||||
"""Handle `cya memory ...` before the root REQUEST argument sees it."""
|
||||
import argparse
|
||||
|
||||
argv = list(sys.argv if argv is None else argv)
|
||||
if len(argv) < 2 or argv[1] != "memory":
|
||||
return False
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f"{argv[0]} memory",
|
||||
description="Inspect and manage user-controlled memory.",
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
reflections = sub.add_parser("reflections", help="List or export Profile 1 reflections.")
|
||||
reflections.add_argument(
|
||||
"--scope",
|
||||
"-s",
|
||||
default=".",
|
||||
help="Directory or scope to list reflections for.",
|
||||
)
|
||||
reflections.add_argument(
|
||||
"--json",
|
||||
dest="export_json",
|
||||
action="store_true",
|
||||
help="Export reflections as JSON.",
|
||||
)
|
||||
args = parser.parse_args(argv[2:])
|
||||
|
||||
if args.command == "reflections":
|
||||
memory_reflections(scope=args.scope, export_json=args.export_json)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _dispatch_retrospect_argv(argv: list[str] | None = None) -> bool:
|
||||
"""Handle `cya retrospect ...` before the root REQUEST argument sees it."""
|
||||
import argparse
|
||||
|
||||
argv = list(sys.argv if argv is None else argv)
|
||||
if len(argv) < 2 or argv[1] != "retrospect":
|
||||
return False
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog=f"{argv[0]} retrospect",
|
||||
description="Start a guided retrospection session.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--scope",
|
||||
"-s",
|
||||
default=".",
|
||||
help="Directory or scope to run retrospection on.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--limit",
|
||||
type=int,
|
||||
default=8,
|
||||
help="Number of recent memory items to review.",
|
||||
)
|
||||
args = parser.parse_args(argv[2:])
|
||||
|
||||
from cya.orchestrator import run_retrospection
|
||||
|
||||
run_retrospection(scope=args.scope, limit=args.limit)
|
||||
return True
|
||||
|
||||
|
||||
def run() -> None:
|
||||
@@ -119,4 +380,10 @@ def run() -> None:
|
||||
Using a thin wrapper around app() lets us keep the full-featured
|
||||
@app.callback(invoke_without_command=True) + ctx signature for Typer/Click.
|
||||
"""
|
||||
if _dispatch_shell_argv() or _dispatch_memory_argv() or _dispatch_retrospect_argv():
|
||||
return
|
||||
app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
263
src/cya/config.py
Normal file
263
src/cya/config.py
Normal file
@@ -0,0 +1,263 @@
|
||||
"""User configuration for cya (CYA-WP-0008-T03).
|
||||
|
||||
Reads ``~/.config/cya/config.toml`` and optional project ``.cya.toml``.
|
||||
Environment variables override file values where noted.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
_USER_CONFIG = Path.home() / ".config" / "cya" / "config.toml"
|
||||
_PROJECT_CONFIG_NAME = ".cya.toml"
|
||||
|
||||
# Session context bounds (CYA-WP-0008-T04) — documented in docs/llm-connect-integration.md
|
||||
MAX_SESSION_TURNS = 10
|
||||
MAX_SESSION_CHARS = 4000
|
||||
DEFAULT_SHELL_HISTORY_LINES = 50
|
||||
|
||||
|
||||
def _load_toml(path: Path) -> dict[str, Any]:
|
||||
if not path.is_file():
|
||||
return {}
|
||||
if sys.version_info >= (3, 11):
|
||||
import tomllib
|
||||
|
||||
return tomllib.loads(path.read_text())
|
||||
import tomli
|
||||
|
||||
return tomli.loads(path.read_bytes())
|
||||
|
||||
|
||||
def _find_project_config(start: Path | None = None) -> Path | None:
|
||||
current = (start or Path.cwd()).resolve()
|
||||
for directory in [current, *current.parents]:
|
||||
candidate = directory / _PROJECT_CONFIG_NAME
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def _merge_llm_sections(*sources: dict[str, Any]) -> dict[str, Any]:
|
||||
merged: dict[str, Any] = {}
|
||||
for source in sources:
|
||||
section = source.get("llm")
|
||||
if isinstance(section, dict):
|
||||
merged.update(section)
|
||||
return merged
|
||||
|
||||
|
||||
def _merge_shell_history_sections(*sources: dict[str, Any]) -> dict[str, Any]:
|
||||
merged: dict[str, Any] = {}
|
||||
for source in sources:
|
||||
section = source.get("shell_history")
|
||||
if isinstance(section, dict):
|
||||
merged.update(section)
|
||||
return merged
|
||||
|
||||
|
||||
@dataclass
|
||||
class LLMSettings:
|
||||
"""Resolved LLM adapter settings."""
|
||||
|
||||
adapter: str = "fake" # "fake" | "connect"
|
||||
backend: str = "openrouter"
|
||||
model: str | None = None
|
||||
temperature: float = 0.3
|
||||
max_tokens: int = 2000
|
||||
api_key_env: str | None = None
|
||||
system_prompt: str | None = None
|
||||
configured: bool = False
|
||||
source: str = "default"
|
||||
|
||||
def to_hints(self) -> dict[str, Any]:
|
||||
hints: dict[str, Any] = {
|
||||
"backend": self.backend,
|
||||
"temperature": self.temperature,
|
||||
"max_tokens": self.max_tokens,
|
||||
}
|
||||
if self.model:
|
||||
hints["model"] = self.model
|
||||
if self.api_key_env:
|
||||
hints["api_key_env"] = self.api_key_env
|
||||
return hints
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShellHistorySettings:
|
||||
"""Resolved shell-history collection settings.
|
||||
|
||||
History is intentionally off unless enabled by the CLI or config. The line
|
||||
limit is capped so a session cannot accidentally ingest an unbounded shell
|
||||
history file.
|
||||
"""
|
||||
|
||||
enabled: bool = False
|
||||
limit: int = DEFAULT_SHELL_HISTORY_LINES
|
||||
histfile: str | None = None
|
||||
source: str = "default"
|
||||
requested_limit: int | None = None
|
||||
notes: list[str] = field(default_factory=list)
|
||||
|
||||
|
||||
def _coerce_float(value: Any, default: float) -> float:
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _coerce_int(value: Any, default: int) -> int:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def _coerce_bool(value: Any, default: bool = False) -> bool:
|
||||
if isinstance(value, bool):
|
||||
return value
|
||||
if value is None:
|
||||
return default
|
||||
text = str(value).strip().lower()
|
||||
if text in {"1", "true", "yes", "y", "on"}:
|
||||
return True
|
||||
if text in {"0", "false", "no", "n", "off"}:
|
||||
return False
|
||||
return default
|
||||
|
||||
|
||||
def load_llm_settings(*, offline: bool = False) -> LLMSettings:
|
||||
"""Resolve LLM settings from env, user config, and project config."""
|
||||
if offline:
|
||||
return LLMSettings(adapter="fake", configured=False, source="--offline")
|
||||
|
||||
env_adapter = os.environ.get("CYA_LLM_ADAPTER", "").strip().lower()
|
||||
if env_adapter in ("fake", "connect"):
|
||||
base = LLMSettings(adapter=env_adapter, configured=env_adapter == "connect", source="CYA_LLM_ADAPTER")
|
||||
else:
|
||||
base = LLMSettings()
|
||||
|
||||
user_data = _load_toml(_USER_CONFIG)
|
||||
project_path = _find_project_config()
|
||||
project_data = _load_toml(project_path) if project_path else {}
|
||||
merged = _merge_llm_sections(user_data, project_data)
|
||||
|
||||
if merged:
|
||||
file_adapter = str(merged.get("adapter", "")).strip().lower()
|
||||
if file_adapter in ("fake", "connect") and not env_adapter:
|
||||
base.adapter = file_adapter
|
||||
base.configured = file_adapter == "connect"
|
||||
base.source = str(project_path or _USER_CONFIG)
|
||||
|
||||
backend = merged.get("backend") or merged.get("provider")
|
||||
if backend:
|
||||
base.backend = str(backend)
|
||||
if not env_adapter and file_adapter != "fake":
|
||||
base.adapter = "connect"
|
||||
base.configured = True
|
||||
base.source = str(project_path or _USER_CONFIG)
|
||||
|
||||
if merged.get("model"):
|
||||
base.model = str(merged["model"])
|
||||
base.temperature = _coerce_float(merged.get("temperature"), base.temperature)
|
||||
base.max_tokens = _coerce_int(merged.get("max_tokens"), base.max_tokens)
|
||||
if merged.get("api_key_env"):
|
||||
base.api_key_env = str(merged["api_key_env"])
|
||||
if merged.get("system_prompt"):
|
||||
base.system_prompt = str(merged["system_prompt"])
|
||||
|
||||
env_backend = os.environ.get("CYA_LLM_BACKEND") or os.environ.get("CYA_LLM_PROVIDER")
|
||||
if env_backend:
|
||||
base.backend = env_backend.strip()
|
||||
if base.adapter != "fake":
|
||||
base.adapter = "connect"
|
||||
base.configured = True
|
||||
base.source = "CYA_LLM_BACKEND"
|
||||
|
||||
env_model = os.environ.get("CYA_LLM_MODEL")
|
||||
if env_model:
|
||||
base.model = env_model.strip()
|
||||
if base.adapter != "fake":
|
||||
base.adapter = "connect"
|
||||
base.configured = True
|
||||
base.source = "CYA_LLM_MODEL"
|
||||
|
||||
return base
|
||||
|
||||
|
||||
def load_shell_history_settings(
|
||||
*,
|
||||
with_history: bool = False,
|
||||
history_limit: int | None = None,
|
||||
) -> ShellHistorySettings:
|
||||
"""Resolve optional shell-history collection settings.
|
||||
|
||||
The default is always disabled. Config can enable history with:
|
||||
|
||||
``[shell_history] enabled = true``
|
||||
|
||||
The CLI ``--with-history`` flag also enables it for just that shell session.
|
||||
"""
|
||||
user_data = _load_toml(_USER_CONFIG)
|
||||
project_path = _find_project_config()
|
||||
project_data = _load_toml(project_path) if project_path else {}
|
||||
merged = _merge_shell_history_sections(user_data, project_data)
|
||||
|
||||
settings = ShellHistorySettings()
|
||||
if merged:
|
||||
source = str(project_path or _USER_CONFIG)
|
||||
settings.enabled = _coerce_bool(merged.get("enabled"), False)
|
||||
settings.limit = _coerce_int(merged.get("limit"), settings.limit)
|
||||
settings.histfile = str(merged["histfile"]) if merged.get("histfile") else None
|
||||
settings.source = source
|
||||
|
||||
if with_history:
|
||||
settings.enabled = True
|
||||
settings.source = "--with-history"
|
||||
|
||||
if history_limit is not None:
|
||||
settings.requested_limit = history_limit
|
||||
settings.limit = _coerce_int(history_limit, settings.limit)
|
||||
if with_history:
|
||||
settings.source = "--with-history"
|
||||
|
||||
if settings.limit < 0:
|
||||
settings.notes.append("Negative history limit was treated as 0.")
|
||||
settings.limit = 0
|
||||
|
||||
if settings.limit > DEFAULT_SHELL_HISTORY_LINES:
|
||||
settings.notes.append(
|
||||
f"History limit capped at {DEFAULT_SHELL_HISTORY_LINES} lines."
|
||||
)
|
||||
settings.limit = DEFAULT_SHELL_HISTORY_LINES
|
||||
|
||||
return settings
|
||||
|
||||
|
||||
def bound_session_turns(
|
||||
turns: list[dict[str, str]] | None,
|
||||
*,
|
||||
max_turns: int = MAX_SESSION_TURNS,
|
||||
max_chars: int = MAX_SESSION_CHARS,
|
||||
) -> list[dict[str, str]]:
|
||||
"""Trim session history to a bounded token/line budget for the adapter."""
|
||||
if not turns:
|
||||
return []
|
||||
|
||||
recent = turns[-max_turns:]
|
||||
bounded: list[dict[str, str]] = []
|
||||
used = 0
|
||||
for turn in recent:
|
||||
user = turn.get("user", "")
|
||||
assistant = turn.get("assistant", "")
|
||||
chunk_len = len(user) + len(assistant)
|
||||
if used + chunk_len > max_chars and bounded:
|
||||
break
|
||||
bounded.append({"user": user, "assistant": assistant})
|
||||
used += chunk_len
|
||||
return bounded
|
||||
@@ -17,11 +17,15 @@ from .adapter import (
|
||||
LLMAdapter,
|
||||
FakeLLMAdapter,
|
||||
)
|
||||
from .connect_adapter import LLMConnectAdapter
|
||||
from .factory import get_adapter
|
||||
|
||||
__all__ = [
|
||||
"AssistanceRequest",
|
||||
"AssistanceResponse",
|
||||
"LLMAdapter",
|
||||
"FakeLLMAdapter",
|
||||
"LLMConnectAdapter",
|
||||
"get_adapter",
|
||||
]
|
||||
|
||||
|
||||
138
src/cya/llm/connect_adapter.py
Normal file
138
src/cya/llm/connect_adapter.py
Normal file
@@ -0,0 +1,138 @@
|
||||
"""llm-connect-backed adapter (CYA-WP-0008-T02)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from cya.config import LLMSettings
|
||||
from cya.llm.adapter import AssistanceRequest, AssistanceResponse
|
||||
from cya.llm.prompt import build_assistance_prompt
|
||||
|
||||
_PROVIDER_ENV_KEYS: dict[str, str] = {
|
||||
"openrouter": "OPENROUTER_API_KEY",
|
||||
"openai": "OPENAI_API_KEY",
|
||||
"gemini": "GEMINI_API_KEY",
|
||||
}
|
||||
|
||||
|
||||
class LLMConnectAdapter:
|
||||
"""Delegates to llm-connect while satisfying cya's LLMAdapter protocol."""
|
||||
|
||||
def __init__(self, settings: LLMSettings) -> None:
|
||||
self._settings = settings
|
||||
self._client: Any | None = None
|
||||
self._init_error: str | None = None
|
||||
self._ensure_client()
|
||||
|
||||
def _ensure_client(self) -> None:
|
||||
try:
|
||||
from llm_connect import create_adapter
|
||||
from llm_connect.config import resolve_api_key
|
||||
except ImportError:
|
||||
self._init_error = (
|
||||
"llm-connect is not installed. Install with:\n"
|
||||
" pip install -e ~/llm-connect\n"
|
||||
"or: pip install -e \".[llm]\" after adding llm-connect to your environment."
|
||||
)
|
||||
return
|
||||
|
||||
env_var = self._settings.api_key_env or _PROVIDER_ENV_KEYS.get(
|
||||
self._settings.backend, "OPENROUTER_API_KEY"
|
||||
)
|
||||
api_key = resolve_api_key(env_var=env_var)
|
||||
if self._settings.backend in ("openrouter", "openai", "gemini") and not api_key:
|
||||
self._init_error = (
|
||||
f"No API key found for backend {self._settings.backend!r} "
|
||||
f"(checked env {env_var!r}).\n"
|
||||
"Route credential custody via warden before requesting secrets:\n"
|
||||
" warden route find \"OpenRouter API key\" --json\n"
|
||||
"Then export the key into your environment — never commit it to the repo."
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
self._client = create_adapter(
|
||||
provider=self._settings.backend,
|
||||
model=self._settings.model,
|
||||
api_key=api_key,
|
||||
system_prompt=self._settings.system_prompt,
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 — surface config errors to the user
|
||||
self._init_error = f"Failed to initialize llm-connect adapter: {exc}"
|
||||
|
||||
def complete(self, request: AssistanceRequest) -> AssistanceResponse:
|
||||
if self._init_error or self._client is None:
|
||||
return self._degraded_response(self._init_error or "llm-connect client unavailable.")
|
||||
|
||||
try:
|
||||
from llm_connect.models import RunConfig
|
||||
except ImportError:
|
||||
return self._degraded_response(self._init_error or "llm-connect not installed.")
|
||||
|
||||
system, user_prompt = build_assistance_prompt(
|
||||
request,
|
||||
system_prompt=self._settings.system_prompt,
|
||||
)
|
||||
hints = {**self._settings.to_hints(), **request.hints}
|
||||
run_config = RunConfig(
|
||||
model_name=hints.get("model") or self._settings.model or "anthropic/claude-sonnet-4",
|
||||
temperature=float(hints.get("temperature", self._settings.temperature)),
|
||||
max_tokens=int(hints.get("max_tokens", self._settings.max_tokens)),
|
||||
)
|
||||
|
||||
# Re-create adapter when per-request system prompt differs (llm-connect stores it at init).
|
||||
client = self._client
|
||||
if system and not self._settings.system_prompt:
|
||||
from llm_connect import create_adapter
|
||||
from llm_connect.config import resolve_api_key
|
||||
|
||||
env_var = self._settings.api_key_env or _PROVIDER_ENV_KEYS.get(
|
||||
self._settings.backend, "OPENROUTER_API_KEY"
|
||||
)
|
||||
api_key = resolve_api_key(env_var=env_var)
|
||||
client = create_adapter(
|
||||
provider=self._settings.backend,
|
||||
model=self._settings.model,
|
||||
api_key=api_key,
|
||||
system_prompt=system,
|
||||
)
|
||||
|
||||
try:
|
||||
llm_response = client.execute_prompt(user_prompt, run_config)
|
||||
except Exception as exc: # noqa: BLE001 — user-facing degrade path
|
||||
return self._degraded_response(
|
||||
f"llm-connect request failed: {exc}",
|
||||
partial_raw=str(exc),
|
||||
)
|
||||
|
||||
content = (llm_response.content or "").strip()
|
||||
return AssistanceResponse(
|
||||
suggestion=content or "(empty model response)",
|
||||
explanation="Response generated via llm-connect.",
|
||||
rationale="Model inference using configured backend and bounded local context.",
|
||||
risks=[],
|
||||
raw_model_output=content,
|
||||
metadata={
|
||||
"adapter": "LLMConnectAdapter",
|
||||
"backend": self._settings.backend,
|
||||
"model": llm_response.model,
|
||||
"usage": llm_response.usage,
|
||||
"finish_reason": llm_response.finish_reason,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _degraded_response(message: str, *, partial_raw: str | None = None) -> AssistanceResponse:
|
||||
return AssistanceResponse(
|
||||
suggestion=(
|
||||
"cya could not reach a configured LLM backend.\n\n"
|
||||
f"{message}\n\n"
|
||||
"Continuing in offline mode: re-run with `--offline` or configure "
|
||||
"`~/.config/cya/config.toml` (see README)."
|
||||
),
|
||||
explanation="Graceful degradation — no live inference was performed.",
|
||||
rationale="llm-connect unavailable or misconfigured.",
|
||||
risks=["No live model inference"],
|
||||
raw_model_output=partial_raw,
|
||||
metadata={"adapter": "LLMConnectAdapter", "degraded": True},
|
||||
)
|
||||
18
src/cya/llm/factory.py
Normal file
18
src/cya/llm/factory.py
Normal file
@@ -0,0 +1,18 @@
|
||||
"""Adapter selection factory (CYA-WP-0008-T04)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from cya.config import LLMSettings, load_llm_settings
|
||||
from cya.llm.adapter import FakeLLMAdapter, LLMAdapter
|
||||
from cya.llm.connect_adapter import LLMConnectAdapter
|
||||
|
||||
|
||||
def get_adapter(*, offline: bool = False, settings: LLMSettings | None = None) -> LLMAdapter:
|
||||
"""Return the active LLMAdapter for one-shot and shell code paths."""
|
||||
resolved = settings or load_llm_settings(offline=offline)
|
||||
if resolved.adapter == "connect":
|
||||
return LLMConnectAdapter(resolved)
|
||||
return FakeLLMAdapter()
|
||||
|
||||
|
||||
__all__ = ["get_adapter", "load_llm_settings"]
|
||||
73
src/cya/llm/prompt.py
Normal file
73
src/cya/llm/prompt.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Prompt construction for llm-connect delegation (CYA-WP-0008)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
from cya.llm.adapter import AssistanceRequest
|
||||
|
||||
_DEFAULT_SYSTEM = """You are cya, a console-native assistant for practical local work from the shell.
|
||||
|
||||
Help the user with command-line tasks: repository inspection, file workflows, command
|
||||
suggestion, command explanation, and local context summarization.
|
||||
|
||||
Be concise and practical. When suggesting shell commands, explain risks briefly.
|
||||
Do not claim to have executed anything — the user runs commands themselves.
|
||||
Reference the provided context when it is relevant."""
|
||||
|
||||
|
||||
def default_system_prompt() -> str:
|
||||
return _DEFAULT_SYSTEM
|
||||
|
||||
|
||||
def build_assistance_prompt(request: AssistanceRequest, *, system_prompt: str | None = None) -> tuple[str, str]:
|
||||
"""Return (system_prompt, user_prompt) for llm-connect execute_prompt."""
|
||||
system = system_prompt or default_system_prompt()
|
||||
parts: list[str] = []
|
||||
|
||||
context = request.context or {}
|
||||
session_turns = context.get("session_turns")
|
||||
if session_turns:
|
||||
parts.append("## Recent conversation")
|
||||
for turn in session_turns:
|
||||
parts.append(f"User: {turn.get('user', '')}")
|
||||
parts.append(f"Assistant: {turn.get('assistant', '')}")
|
||||
|
||||
envelope = {k: v for k, v in context.items() if k not in ("session_turns", "memory")}
|
||||
if envelope:
|
||||
parts.append("## Local context")
|
||||
parts.append(_summarize_context(envelope))
|
||||
|
||||
memory = context.get("memory")
|
||||
if isinstance(memory, dict) and memory.get("items"):
|
||||
parts.append("## Activated memory")
|
||||
for item in memory["items"][:8]:
|
||||
parts.append(f"- [{item.get('kind', '?')}] {item.get('key', '?')}: {item.get('value', '')}")
|
||||
|
||||
parts.append("## Current request")
|
||||
parts.append(request.user_request.strip())
|
||||
|
||||
return system, "\n\n".join(parts)
|
||||
|
||||
|
||||
def _summarize_context(envelope: dict[str, Any]) -> str:
|
||||
"""Compact, JSON-safe context summary to stay within prompt budget."""
|
||||
summary: dict[str, Any] = {}
|
||||
if envelope.get("cwd"):
|
||||
summary["cwd"] = envelope["cwd"]
|
||||
if envelope.get("git"):
|
||||
git = envelope["git"]
|
||||
summary["git"] = {
|
||||
k: git[k]
|
||||
for k in ("branch", "status_short", "workdir", "is_repo")
|
||||
if k in git
|
||||
}
|
||||
if envelope.get("top_level"):
|
||||
names = [e.get("name") for e in envelope["top_level"][:30] if e.get("name")]
|
||||
summary["top_level"] = names
|
||||
if envelope.get("env"):
|
||||
summary["env"] = envelope["env"]
|
||||
if envelope.get("notes"):
|
||||
summary["notes"] = envelope["notes"][:5]
|
||||
return json.dumps(summary, indent=2, default=str)
|
||||
@@ -1,83 +1,304 @@
|
||||
"""phase-memory ports (T05) — strictly minimal no-op version.
|
||||
"""phase-memory ports — cya seam to phase-memory (markitect).
|
||||
|
||||
Operator direction (2026-05-26): Keep strictly minimal in this slice.
|
||||
Pure explicit ports with no-op implementations and clear
|
||||
"to be replaced by real phase-memory integration" markers.
|
||||
**No local JSON placeholder or file-backed store in this slice.**
|
||||
This module is the explicit, inspectable boundary. All memory for
|
||||
assistance (preferences, project context, retrospection outcomes, etc.)
|
||||
flows through here.
|
||||
|
||||
All memory interactions in can-you-assist must go through these ports.
|
||||
No global singletons, no implicit ~/.cache, no opaque vendor memory.
|
||||
**Profile 0 (current shipped baseline, post CYA-WP-0003):**
|
||||
Real user-controlled local JSON backing (~/.config/cya/memory/<scope>.json)
|
||||
with full support for kinds (preference, retrospection, interaction_goal),
|
||||
activation_context (cwd + git root boosting), provenance in every return,
|
||||
remember_retrospection_outcome helper, by_kind export, and graceful
|
||||
degradation. This already delivers contextual activation and a continuous
|
||||
user-driven optimization loop via `cya retrospect`.
|
||||
|
||||
When the real `phase-memory` package is integrated, the entire contents
|
||||
of this module (or the implementations behind these names) will be
|
||||
replaced by the real ports. Code reviewers and future contributors
|
||||
should be able to point at this file and say "this is the seam".
|
||||
See:
|
||||
- MemoryVision.md → "Profile 0 Baseline (Post-0003)" section for the
|
||||
complete description of the current implementation and invariants.
|
||||
- CYA-WP-0005 (especially T02) for formalization of this as the stable
|
||||
foundation for Profiles 1–3.
|
||||
- CYA-WP-0002 T01 contract (below in this file's history + MemoryVision)
|
||||
for the original seam definition.
|
||||
|
||||
See workplan CYA-WP-0001-T05 for the full contract and acceptance criteria.
|
||||
The public API signatures are stable. Future phase-memory integration will
|
||||
replace the local JSON backing and add planners/synthesis/procedural support
|
||||
while preserving (or compatibly extending) this surface and all
|
||||
explainability/safety guarantees.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
# Standard memory kinds used by cya (especially after CYA-WP-0003)
|
||||
KIND_PREFERENCE = "preference"
|
||||
KIND_RETROSPECTION = "retrospection"
|
||||
KIND_INTERACTION_GOAL = "interaction_goal"
|
||||
KIND_REFLECTION = "reflection" # Profile 1 (Reflexion-style verbal lessons) — T05 minimal spike
|
||||
KIND_SESSION_TURN = "session_turn" # CYA-WP-0007 episodic shell turn precursor
|
||||
|
||||
|
||||
def _warn_not_connected(feature: str) -> None:
|
||||
"""Loud, visible marker that phase-memory is not yet wired."""
|
||||
"""Loud, visible marker that phase-memory is not yet wired (fallback path)."""
|
||||
msg = (
|
||||
f"[phase-memory] {feature} called — phase-memory not yet connected. "
|
||||
"This is a no-op placeholder. Real implementation will come from the "
|
||||
"phase-memory package. See T05 in workplan CYA-WP-0001."
|
||||
"Falling back to local T02 json store (real phase graph in later slice). "
|
||||
"See T02 in workplan CYA-WP-0002 and MemoryVision contract."
|
||||
)
|
||||
print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Explicit ports (the four capabilities from the workplan)
|
||||
# These are the exact extension points that phase-memory will implement.
|
||||
# Real T02 backing (user-controlled, explainable, phase-ready)
|
||||
# Uses stdlib + optional phase_memory.models for structure.
|
||||
# Persists across cya invocations via ~/.config/cya/memory/<scope>.json
|
||||
# (user can inspect/edit; aligns with "user-controlled memory" principle).
|
||||
# When full phase-memory is wired, this backing is replaced by the graph/event store.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def remember_preference(key: str, value: Any, scope: str = "cwd") -> None:
|
||||
"""Remember a user preference or workflow pattern.
|
||||
def _mem_path(scope: str = "cwd") -> Path:
|
||||
p = Path.home() / ".config" / "cya" / "memory"
|
||||
p.mkdir(parents=True, exist_ok=True)
|
||||
return p / f"{scope}.json"
|
||||
|
||||
Will be replaced by real phase-memory.
|
||||
|
||||
def _load(scope: str) -> list[dict[str, Any]]:
|
||||
f = _mem_path(scope)
|
||||
if f.exists():
|
||||
try:
|
||||
data = json.loads(f.read_text(encoding="utf-8"))
|
||||
return data if isinstance(data, list) else []
|
||||
except Exception:
|
||||
return []
|
||||
return []
|
||||
|
||||
|
||||
def _save(scope: str, items: list[dict[str, Any]]) -> None:
|
||||
f = _mem_path(scope)
|
||||
f.write_text(json.dumps(items, indent=2, default=str), encoding="utf-8")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Explicit ports (the four capabilities from the workplan)
|
||||
# Refined in T01 (0003) per phase-memory architecture + interop + lifecycle.
|
||||
# Real (non-no-op) implementation added in T02 (0002): actual persist + recall.
|
||||
# Extended in T02 (0003) for contextual activation and retrospection support.
|
||||
# See docs/cya-memory-activation-and-retrospection-concept.md and MemoryVision.md.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def remember_preference(
|
||||
key: str,
|
||||
value: Any,
|
||||
scope: str = "cwd",
|
||||
*,
|
||||
profile: str | None = None,
|
||||
ttl: str | None = None,
|
||||
kind: str = KIND_PREFERENCE,
|
||||
provenance: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Remember a user preference, workflow pattern, retrospection outcome, or goal.
|
||||
|
||||
`kind` defaults to "preference" for backward compatibility.
|
||||
Special kinds (e.g. "retrospection", "interaction_goal") are supported for
|
||||
higher-order memory from retrospection sessions (see CYA-WP-0003).
|
||||
|
||||
Real T02 + 0003: persists to user-controlled json (scoped).
|
||||
"""
|
||||
_warn_not_connected(f"remember_preference({key!r}, scope={scope})")
|
||||
# No-op by design
|
||||
try:
|
||||
items = _load(scope)
|
||||
item = {
|
||||
"key": key,
|
||||
"value": value,
|
||||
"ts": time.time(),
|
||||
"scope": scope,
|
||||
"profile": profile,
|
||||
"kind": kind,
|
||||
}
|
||||
if provenance:
|
||||
item["provenance"] = provenance
|
||||
# avoid exact dups for same key in small stores
|
||||
items = [i for i in items if i.get("key") != key]
|
||||
items.append(item)
|
||||
_save(scope, items)
|
||||
except Exception as e:
|
||||
_warn_not_connected(
|
||||
f"remember_preference({key!r}, scope={scope}, profile={profile}) err={e}"
|
||||
)
|
||||
|
||||
|
||||
def recall_preferences(scope: str = "cwd", task_class: str | None = None) -> dict[str, Any]:
|
||||
"""Recall relevant history / preferences for the current cwd + task class.
|
||||
def recall_preferences(
|
||||
scope: str = "cwd",
|
||||
task_class: str | None = None,
|
||||
*,
|
||||
kinds: list[str] | None = None,
|
||||
profile: str | None = None,
|
||||
limit: int = 50,
|
||||
activation_context: dict | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Recall relevant memory for the current context.
|
||||
|
||||
Will be replaced by real phase-memory.
|
||||
Returns empty dict in this slice.
|
||||
Supports:
|
||||
- `kinds`: filter or boost specific kinds (e.g. ["interaction_goal", "retrospection"])
|
||||
- `activation_context`: hints for smarter activation (e.g. {"cwd": "...", "project": "..."})
|
||||
- Backward compatible with all previous call sites.
|
||||
|
||||
Real T02 + 0003 extensions for contextual activation and retrospection support.
|
||||
"""
|
||||
_warn_not_connected(f"recall_preferences(scope={scope}, task={task_class})")
|
||||
return {}
|
||||
try:
|
||||
items = _load(scope)
|
||||
|
||||
# Basic kind filtering (existing behavior + 0003 enhancement)
|
||||
if kinds:
|
||||
items = [i for i in items if i.get("kind") in kinds or not i.get("kind")]
|
||||
|
||||
# Simple activation boost: prefer items whose scope or profile matches context
|
||||
if activation_context:
|
||||
ctx_cwd = activation_context.get("cwd") or activation_context.get("scope")
|
||||
ctx_profile = activation_context.get("profile")
|
||||
boosted = []
|
||||
normal = []
|
||||
for item in items:
|
||||
if (ctx_cwd and item.get("scope") == ctx_cwd) or (ctx_profile and item.get("profile") == ctx_profile):
|
||||
boosted.append(item)
|
||||
else:
|
||||
normal.append(item)
|
||||
items = boosted + normal
|
||||
|
||||
# Profile 1: prefer reflections ahead of other kinds when requested
|
||||
if kinds and KIND_REFLECTION in kinds:
|
||||
reflections = [i for i in items if i.get("kind") == KIND_REFLECTION]
|
||||
others = [i for i in items if i.get("kind") != KIND_REFLECTION]
|
||||
items = (reflections + others)[:limit]
|
||||
else:
|
||||
items = items[-limit:] # most recent (after boosting)
|
||||
|
||||
return {
|
||||
"items": items,
|
||||
"provenance": [
|
||||
{
|
||||
"source": "cya-local-memory-json (T02+0003; activation + retrospection support)",
|
||||
"scope": scope,
|
||||
"count": len(items),
|
||||
"activation_context": activation_context,
|
||||
}
|
||||
],
|
||||
"phase": "fluid",
|
||||
"profile": profile,
|
||||
"note": "real persist + contextual activation; full phase-memory in later slices",
|
||||
}
|
||||
except Exception as e:
|
||||
_warn_not_connected(
|
||||
f"recall_preferences(scope={scope}, task={task_class}, profile={profile}) err={e}"
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
def forget(scope: str = "cwd", keys: list[str] | None = None) -> None:
|
||||
def forget(scope: str = "cwd", keys: list[str] | None = None, *, profile: str | None = None) -> None:
|
||||
"""Forget / reset memory (scoped).
|
||||
|
||||
Will be replaced by real phase-memory.
|
||||
Real T02: removes from the user json.
|
||||
Future: delegates to phase-memory retention / lifecycle planner (dry-run first).
|
||||
"""
|
||||
_warn_not_connected(f"forget(scope={scope}, keys={keys})")
|
||||
# No-op
|
||||
try:
|
||||
if not keys:
|
||||
_save(scope, [])
|
||||
return
|
||||
items = _load(scope)
|
||||
items = [i for i in items if i.get("key") not in keys]
|
||||
_save(scope, items)
|
||||
except Exception as e:
|
||||
_warn_not_connected(f"forget(scope={scope}, keys={keys}, profile={profile}) err={e}")
|
||||
|
||||
|
||||
def export_memory(scope: str = "cwd") -> dict[str, Any]:
|
||||
def export_memory(scope: str = "cwd", *, profile: str | None = None, kinds: list[str] | None = None) -> dict[str, Any]:
|
||||
"""Inspect / export current memory for this project or user.
|
||||
|
||||
Will be replaced by real phase-memory.
|
||||
Returns a clear "disabled" marker in this slice.
|
||||
Supports optional `kinds` filter (e.g. to export only retrospection records).
|
||||
Real T02 + 0003: improved transparency for different memory kinds.
|
||||
"""
|
||||
_warn_not_connected(f"export_memory(scope={scope})")
|
||||
return {
|
||||
"status": "phase-memory not connected (T05 no-op)",
|
||||
"scope": scope,
|
||||
"note": "Replace this entire module with the real phase-memory ports.",
|
||||
}
|
||||
try:
|
||||
items = _load(scope)
|
||||
if kinds:
|
||||
items = [i for i in items if i.get("kind") in kinds]
|
||||
|
||||
by_kind = {}
|
||||
for item in items:
|
||||
k = item.get("kind", "unknown")
|
||||
by_kind.setdefault(k, []).append(item)
|
||||
|
||||
reflection_by_scope: dict[str, int] = {}
|
||||
for item in items:
|
||||
if item.get("kind") == KIND_REFLECTION:
|
||||
s = str(item.get("scope", scope))
|
||||
reflection_by_scope[s] = reflection_by_scope.get(s, 0) + 1
|
||||
|
||||
result = {
|
||||
"status": "real (T02+0003 local json; activation + retrospection ready)",
|
||||
"scope": scope,
|
||||
"profile": profile,
|
||||
"count": len(items),
|
||||
"items": items,
|
||||
"by_kind": {k: len(v) for k, v in by_kind.items()},
|
||||
"phase": "fluid",
|
||||
"provenance_summary": f"{len(items)} records from ~/.config/cya/memory/ (kinds: {list(by_kind.keys())})",
|
||||
"note": "User-controlled. Replace with real phase-memory when available.",
|
||||
"phases": ["ephemeral", "fluid", "stabilized", "rigid"],
|
||||
}
|
||||
if reflection_by_scope:
|
||||
result["reflection_counts_by_scope"] = reflection_by_scope
|
||||
result["reflection_count"] = sum(reflection_by_scope.values())
|
||||
return result
|
||||
except Exception as e:
|
||||
_warn_not_connected(f"export_memory(scope={scope}, profile={profile}) err={e}")
|
||||
return {
|
||||
"status": "error",
|
||||
"error": str(e),
|
||||
}
|
||||
|
||||
|
||||
def remember_retrospection_outcome(
|
||||
key: str,
|
||||
value: Any,
|
||||
scope: str = "cwd",
|
||||
*,
|
||||
profile: str | None = None,
|
||||
) -> None:
|
||||
"""Convenience helper to record outcomes from a retrospection session.
|
||||
|
||||
Stores with kind="retrospection" or "interaction_goal" (caller decides via key/value).
|
||||
This is a thin wrapper around remember_preference for clarity in T04+.
|
||||
"""
|
||||
kind = KIND_RETROSPECTION if "retrospection" in str(key).lower() else KIND_INTERACTION_GOAL
|
||||
remember_preference(key, value, scope=scope, profile=profile, kind=kind)
|
||||
|
||||
|
||||
def remember_reflection(
|
||||
key: str,
|
||||
value: Any,
|
||||
scope: str = "cwd",
|
||||
*,
|
||||
profile: str | None = None,
|
||||
provenance: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
"""Convenience helper for Profile 1 (Reflexion-style verbal self-improvement).
|
||||
|
||||
Stores verbal lessons/reflections with kind="reflection" for preferential
|
||||
activation in future turns.
|
||||
"""
|
||||
remember_preference(
|
||||
key,
|
||||
value,
|
||||
scope=scope,
|
||||
profile=profile,
|
||||
kind=KIND_REFLECTION,
|
||||
provenance=provenance,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
@@ -85,5 +306,12 @@ __all__ = [
|
||||
"recall_preferences",
|
||||
"forget",
|
||||
"export_memory",
|
||||
"remember_retrospection_outcome",
|
||||
"remember_reflection",
|
||||
"KIND_PREFERENCE",
|
||||
"KIND_RETROSPECTION",
|
||||
"KIND_INTERACTION_GOAL",
|
||||
"KIND_REFLECTION",
|
||||
"KIND_SESSION_TURN",
|
||||
]
|
||||
|
||||
|
||||
241
src/cya/memory/reflections.py
Normal file
241
src/cya/memory/reflections.py
Normal file
@@ -0,0 +1,241 @@
|
||||
"""Profile 1 reflection helpers — capture, compaction, and surfacing.
|
||||
|
||||
Pure, testable logic for verbal reflection management. Interactive CLI flows
|
||||
in orchestrator.py call into these functions.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
from difflib import SequenceMatcher
|
||||
from typing import Any
|
||||
|
||||
from cya.memory import (
|
||||
KIND_REFLECTION,
|
||||
_load,
|
||||
_save,
|
||||
export_memory,
|
||||
remember_reflection,
|
||||
)
|
||||
|
||||
REFLECTION_CAPTURE_PROMPTS: tuple[tuple[str, str], ...] = (
|
||||
("went_well", "What went well that you want to remember? (or 'skip')"),
|
||||
("remember", "What should cya remember for next time? (or 'skip')"),
|
||||
("avoid", "What should cya avoid in this scope? (or 'skip')"),
|
||||
)
|
||||
|
||||
_SKIP_VALUES = frozenset({"", "skip", "s", "n", "no"})
|
||||
_MAX_SURFACING_LESSONS = 3
|
||||
_MAX_LESSON_CHARS_EXPLAIN = 80
|
||||
_MAX_LESSON_CHARS_RESPONSE = 60
|
||||
_SIMILARITY_THRESHOLD = 0.85
|
||||
|
||||
|
||||
def is_skip_answer(text: str) -> bool:
|
||||
return not text or text.strip().lower() in _SKIP_VALUES
|
||||
|
||||
|
||||
def collect_lessons_from_answers(answers: dict[str, str]) -> list[dict[str, str]]:
|
||||
"""Build lesson records from guided prompt answers; skips empty/skip answers."""
|
||||
lessons: list[dict[str, str]] = []
|
||||
for prompt_key, _label in REFLECTION_CAPTURE_PROMPTS:
|
||||
raw = answers.get(prompt_key, "")
|
||||
if is_skip_answer(raw):
|
||||
continue
|
||||
text = raw.strip()
|
||||
if text:
|
||||
lessons.append({"prompt": prompt_key, "text": text})
|
||||
return lessons
|
||||
|
||||
|
||||
def preview_lessons(lessons: list[dict[str, str]]) -> str:
|
||||
if not lessons:
|
||||
return "(no lessons to save)"
|
||||
lines = []
|
||||
for i, lesson in enumerate(lessons, 1):
|
||||
label = lesson.get("prompt", "lesson").replace("_", " ")
|
||||
lines.append(f"{i}. [{label}] {lesson['text']}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def session_provenance(scope: str) -> dict[str, Any]:
|
||||
return {
|
||||
"session_date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
||||
"scope": scope,
|
||||
"source": "cya retrospect",
|
||||
}
|
||||
|
||||
|
||||
def save_reflection_lessons(
|
||||
lessons: list[dict[str, str]],
|
||||
scope: str,
|
||||
*,
|
||||
provenance: dict[str, Any] | None = None,
|
||||
) -> int:
|
||||
"""Persist confirmed lessons; returns count saved. No-op for empty input."""
|
||||
if not lessons:
|
||||
return 0
|
||||
meta = provenance or session_provenance(scope)
|
||||
saved = 0
|
||||
for lesson in lessons:
|
||||
key = f"reflection_{lesson['prompt']}_{meta['session_date']}"
|
||||
remember_reflection(
|
||||
key,
|
||||
lesson["text"],
|
||||
scope=scope,
|
||||
provenance={**meta, "prompt": lesson["prompt"]},
|
||||
)
|
||||
saved += 1
|
||||
return saved
|
||||
|
||||
|
||||
def normalize_reflection_text(text: str) -> str:
|
||||
collapsed = re.sub(r"\s+", " ", text.strip().lower())
|
||||
return re.sub(r"[^\w\s]", "", collapsed)
|
||||
|
||||
|
||||
def reflection_similarity(a: str, b: str) -> float:
|
||||
na, nb = normalize_reflection_text(a), normalize_reflection_text(b)
|
||||
if not na or not nb:
|
||||
return 0.0
|
||||
if na == nb:
|
||||
return 1.0
|
||||
return SequenceMatcher(None, na, nb).ratio()
|
||||
|
||||
|
||||
def find_duplicate_reflection_groups(
|
||||
scope: str,
|
||||
*,
|
||||
threshold: float = _SIMILARITY_THRESHOLD,
|
||||
) -> list[list[dict[str, Any]]]:
|
||||
"""Return groups of near-duplicate reflection items in the same scope."""
|
||||
items = [i for i in _load(scope) if i.get("kind") == KIND_REFLECTION]
|
||||
groups: list[list[dict[str, Any]]] = []
|
||||
used: set[str] = set()
|
||||
|
||||
for i, item in enumerate(items):
|
||||
key_i = item.get("key", str(i))
|
||||
if key_i in used:
|
||||
continue
|
||||
group = [item]
|
||||
val_i = str(item.get("value", ""))
|
||||
for j, other in enumerate(items):
|
||||
if j <= i:
|
||||
continue
|
||||
key_j = other.get("key", str(j))
|
||||
if key_j in used:
|
||||
continue
|
||||
val_j = str(other.get("value", ""))
|
||||
if reflection_similarity(val_i, val_j) >= threshold:
|
||||
group.append(other)
|
||||
used.add(key_j)
|
||||
if len(group) > 1:
|
||||
groups.append(group)
|
||||
used.add(key_i)
|
||||
return groups
|
||||
|
||||
|
||||
def compact_reflections(
|
||||
scope: str,
|
||||
*,
|
||||
keep_key: str,
|
||||
remove_keys: list[str],
|
||||
merged_value: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Explicit opt-in compaction: update keeper and remove duplicates."""
|
||||
items = _load(scope)
|
||||
removed: list[str] = []
|
||||
updated = False
|
||||
|
||||
for item in items:
|
||||
if item.get("key") == keep_key and item.get("kind") == KIND_REFLECTION:
|
||||
if merged_value is not None:
|
||||
item["value"] = merged_value
|
||||
updated = True
|
||||
|
||||
new_items = []
|
||||
for item in items:
|
||||
k = item.get("key")
|
||||
if k in remove_keys and item.get("kind") == KIND_REFLECTION:
|
||||
removed.append(k)
|
||||
continue
|
||||
new_items.append(item)
|
||||
|
||||
if removed or updated:
|
||||
_save(scope, new_items)
|
||||
|
||||
return {
|
||||
"kept": keep_key,
|
||||
"removed": removed,
|
||||
"updated": updated,
|
||||
"remaining_reflections": sum(
|
||||
1 for i in new_items if i.get("kind") == KIND_REFLECTION
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def list_reflections(scope: str) -> list[dict[str, Any]]:
|
||||
exported = export_memory(scope, kinds=[KIND_REFLECTION])
|
||||
return exported.get("items", [])
|
||||
|
||||
|
||||
def format_reflection_surfacing(
|
||||
memory: dict[str, Any],
|
||||
*,
|
||||
for_explain: bool = False,
|
||||
) -> str | None:
|
||||
"""Budget-capped reflection summary for explain-context or normal responses."""
|
||||
items = memory.get("items", []) if isinstance(memory, dict) else []
|
||||
reflections = [i for i in items if i.get("kind") == KIND_REFLECTION]
|
||||
if not reflections:
|
||||
return None
|
||||
|
||||
max_chars = _MAX_LESSON_CHARS_EXPLAIN if for_explain else _MAX_LESSON_CHARS_RESPONSE
|
||||
snippets: list[str] = []
|
||||
for item in reflections[:_MAX_SURFACING_LESSONS]:
|
||||
text = str(item.get("value", "")).strip()
|
||||
if len(text) > max_chars:
|
||||
text = text[: max_chars - 3] + "..."
|
||||
prov = item.get("provenance") or {}
|
||||
date = prov.get("session_date", "")
|
||||
prefix = f"({date}) " if date and for_explain else ""
|
||||
snippets.append(f"{prefix}{text}")
|
||||
|
||||
extra = len(reflections) - _MAX_SURFACING_LESSONS
|
||||
suffix = f" (+{extra} more)" if extra > 0 else ""
|
||||
count = len(reflections)
|
||||
noun = "reflection" if count == 1 else "reflections"
|
||||
return f"{count} verbal {noun} influenced this{suffix}: " + "; ".join(snippets)
|
||||
|
||||
|
||||
def reflection_export_stats(scope: str) -> dict[str, Any]:
|
||||
"""Observability: reflection counts and scope breakdown for export."""
|
||||
exported = export_memory(scope, kinds=[KIND_REFLECTION])
|
||||
items = exported.get("items", [])
|
||||
by_scope: dict[str, int] = {}
|
||||
for item in items:
|
||||
s = item.get("scope", scope)
|
||||
by_scope[s] = by_scope.get(s, 0) + 1
|
||||
return {
|
||||
"reflection_count": len(items),
|
||||
"reflection_counts_by_scope": by_scope,
|
||||
"scope": scope,
|
||||
}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"REFLECTION_CAPTURE_PROMPTS",
|
||||
"collect_lessons_from_answers",
|
||||
"preview_lessons",
|
||||
"session_provenance",
|
||||
"save_reflection_lessons",
|
||||
"normalize_reflection_text",
|
||||
"reflection_similarity",
|
||||
"find_duplicate_reflection_groups",
|
||||
"compact_reflections",
|
||||
"list_reflections",
|
||||
"format_reflection_surfacing",
|
||||
"reflection_export_stats",
|
||||
"is_skip_answer",
|
||||
]
|
||||
@@ -1,41 +1,107 @@
|
||||
"""Assistance orchestrator (T06).
|
||||
"""Assistance orchestrator.
|
||||
|
||||
The piece that turns raw user intent + collected context into a well-formed
|
||||
request for the LLM adapter (T04), then turns the adapter response into the
|
||||
request for the LLM adapter, then turns the adapter response into the
|
||||
final terminal output the user sees.
|
||||
|
||||
Responsibilities in this slice:
|
||||
- Own the end-to-end happy path after Typer argument parsing.
|
||||
- Coordinate context collector (T02), risk classifier (T03), and LLMAdapter (T04).
|
||||
- Keep the CLI surface (main.py) thin — it should only do argument parsing,
|
||||
help/version, and delegation to this orchestrator.
|
||||
- Be testable in isolation with the FakeLLMAdapter (critical for T07).
|
||||
Key responsibilities:
|
||||
- Coordinate context collector, memory (Profile 0 via recall with activation_context),
|
||||
rule-based risk classifier, and LLMAdapter.
|
||||
- Wire memory influence into every assistance cycle and `--explain-context`.
|
||||
- Own the `cya retrospect` flow that feeds higher-order memory back into the system.
|
||||
- Keep the CLI surface thin.
|
||||
|
||||
This module is the natural home for future prompt framing, context packing
|
||||
with token awareness, safety charter injection, and response post-processing.
|
||||
**Memory note (Profile 0):** This module is the primary consumer of the
|
||||
stable memory ports defined in src/cya/memory/__init__.py. All calls use
|
||||
activation_context derived from the current working directory + git root.
|
||||
See MemoryVision.md "Profile 0 Baseline" and CYA-WP-0005 for the evolution
|
||||
path to Profiles 1–3.
|
||||
|
||||
See workplan CYA-WP-0001-T06.
|
||||
See workplan CYA-WP-0001 (core) + CYA-WP-0003 (memory wiring + retrospect)
|
||||
+ CYA-WP-0005 (profile formalization).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
|
||||
from cya.context.collector import collect, render_explanation
|
||||
from cya.memory import (
|
||||
recall_preferences,
|
||||
remember_retrospection_outcome,
|
||||
KIND_RETROSPECTION,
|
||||
KIND_INTERACTION_GOAL,
|
||||
KIND_REFLECTION,
|
||||
)
|
||||
from cya.memory.reflections import (
|
||||
REFLECTION_CAPTURE_PROMPTS,
|
||||
collect_lessons_from_answers,
|
||||
compact_reflections,
|
||||
find_duplicate_reflection_groups,
|
||||
format_reflection_surfacing,
|
||||
preview_lessons,
|
||||
save_reflection_lessons,
|
||||
session_provenance,
|
||||
)
|
||||
from cya.safety.risk import classify, get_user_confirmation
|
||||
from cya.llm.adapter import AssistanceRequest, FakeLLMAdapter
|
||||
from cya.config import bound_session_turns
|
||||
from cya.llm.adapter import AssistanceRequest
|
||||
from cya.llm.factory import get_adapter
|
||||
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
@dataclass
|
||||
class OrchestratorResult:
|
||||
"""Structured result from a single assistance turn.
|
||||
|
||||
CLI callers still receive the rich terminal rendering. Interactive clients
|
||||
use this object to persist session turns and explain the last turn.
|
||||
"""
|
||||
|
||||
user_request: str
|
||||
assistant: str
|
||||
explanation: str = ""
|
||||
rationale: str = ""
|
||||
context: dict[str, Any] = field(default_factory=dict)
|
||||
memory: dict[str, Any] = field(default_factory=dict)
|
||||
risk: dict[str, Any] = field(default_factory=dict)
|
||||
cancelled: bool = False
|
||||
dry_run: bool = False
|
||||
|
||||
|
||||
def _build_assistance_context(
|
||||
envelope: Any,
|
||||
memory: dict[str, Any],
|
||||
*,
|
||||
session_turns: list[dict[str, str]] | None = None,
|
||||
extra_context: dict[str, Any] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
ctx = (envelope.to_dict() if envelope else {}) or {}
|
||||
ctx["memory"] = memory
|
||||
if session_turns:
|
||||
ctx["session_turns"] = bound_session_turns(session_turns)
|
||||
if extra_context:
|
||||
ctx["session"] = extra_context
|
||||
return ctx
|
||||
|
||||
|
||||
def handle_request(
|
||||
user_request: str,
|
||||
*,
|
||||
explain_context: bool = False,
|
||||
dry_run: bool = False,
|
||||
) -> None:
|
||||
offline: bool = False,
|
||||
session_turns: list[dict[str, str]] | None = None,
|
||||
extra_context: dict[str, Any] | None = None,
|
||||
) -> OrchestratorResult:
|
||||
"""Primary orchestrator entry point.
|
||||
|
||||
This is what the CLI (and future tests / other front-ends) should call.
|
||||
@@ -62,8 +128,74 @@ def handle_request(
|
||||
except Exception as exc:
|
||||
console.print(f"[red]Context explanation error: {exc}[/red]")
|
||||
|
||||
# 2. Risk classification + mandatory confirmation (T03)
|
||||
assessment = classify(user_request, envelope)
|
||||
# T03 (memory wiring): consult after context (so safety can see it in future T04 0002),
|
||||
# before risk/LLM. Real T02 prefs now available; graceful.
|
||||
# T03 (0003): pass activation_context so directory/project-bound memory is automatically
|
||||
# activated based on cwd + git root.
|
||||
memory = {}
|
||||
try:
|
||||
act_ctx = {"cwd": str(Path.cwd())}
|
||||
if envelope and getattr(envelope, "git", None):
|
||||
git_info = envelope.git or {}
|
||||
if git_info.get("workdir"):
|
||||
act_ctx["git_root"] = git_info["workdir"]
|
||||
memory = recall_preferences(
|
||||
".",
|
||||
activation_context=act_ctx,
|
||||
kinds=[KIND_REFLECTION, KIND_RETROSPECTION, KIND_INTERACTION_GOAL, "preference"],
|
||||
)
|
||||
except Exception:
|
||||
memory = {"error": "recall failed (graceful degradation)"}
|
||||
|
||||
if explain_context and memory.get("items"):
|
||||
try:
|
||||
prov = memory.get("provenance", [{}])[0]
|
||||
sample = ", ".join(i.get("key", "?") for i in memory.get("items", [])[:3])
|
||||
act_note = ""
|
||||
if prov.get("activation_context"):
|
||||
act_note = f" | ctx: {prov['activation_context']}"
|
||||
body = (
|
||||
f"Phase: {memory.get('phase')} | {len(memory.get('items', []))} items | "
|
||||
f"{prov.get('source', 'local')}{act_note}\n"
|
||||
f"Sample activated: {sample}"
|
||||
)
|
||||
refl_line = format_reflection_surfacing(memory, for_explain=True)
|
||||
if refl_line:
|
||||
body += f"\n\n[cyan]Reflections:[/cyan] {refl_line}"
|
||||
console.print(
|
||||
Panel(
|
||||
body,
|
||||
title="Memory Activated (T03)",
|
||||
border_style="blue",
|
||||
padding=(0, 1),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if explain_context and extra_context:
|
||||
try:
|
||||
import json
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
json.dumps(extra_context, indent=2, default=str),
|
||||
title="Shell Session Context",
|
||||
border_style="cyan",
|
||||
padding=(0, 1),
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Risk classification + mandatory confirmation (T03 safety; T04 memory signals)
|
||||
assessment = classify(user_request, envelope, memory=memory)
|
||||
ctx = _build_assistance_context(
|
||||
envelope,
|
||||
memory,
|
||||
session_turns=session_turns,
|
||||
extra_context=extra_context,
|
||||
)
|
||||
|
||||
if assessment.requires_confirmation:
|
||||
from rich.table import Table
|
||||
@@ -82,27 +214,50 @@ def handle_request(
|
||||
console.print(table)
|
||||
|
||||
if not get_user_confirmation(assessment):
|
||||
console.print("[yellow]Action cancelled by user. No changes made.[/yellow]")
|
||||
return
|
||||
msg = "Action cancelled by user. No changes made."
|
||||
console.print(f"[yellow]{msg}[/yellow]")
|
||||
return OrchestratorResult(
|
||||
user_request=user_request,
|
||||
assistant=msg,
|
||||
context=ctx,
|
||||
memory=memory,
|
||||
risk=assessment.to_dict(),
|
||||
cancelled=True,
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
console.print("[green]--dry-run acknowledged.[/green] No side-effects.")
|
||||
return
|
||||
msg = "--dry-run acknowledged. No side-effects."
|
||||
console.print(f"[green]{msg}[/green]")
|
||||
return OrchestratorResult(
|
||||
user_request=user_request,
|
||||
assistant=msg,
|
||||
context=ctx,
|
||||
memory=memory,
|
||||
risk=assessment.to_dict(),
|
||||
dry_run=True,
|
||||
)
|
||||
|
||||
# 3. Call through the single LLMAdapter boundary (T04)
|
||||
adapter = FakeLLMAdapter()
|
||||
# 3. Call through the single LLMAdapter boundary (T04 / CYA-WP-0008)
|
||||
adapter = get_adapter(offline=offline)
|
||||
llm_request = AssistanceRequest(
|
||||
user_request=user_request,
|
||||
context=envelope.to_dict() if envelope else None,
|
||||
context=ctx,
|
||||
)
|
||||
llm_response = adapter.complete(llm_request)
|
||||
|
||||
# 4. Render final user-facing artifact (T06 responsibility)
|
||||
# 4. Render final user-facing artifact (T06 responsibility; T03 memory surface)
|
||||
mem_line = ""
|
||||
if memory.get("items"):
|
||||
mem_line = f"\n[dim]Memory activated: {len(memory.get('items', []))} items (phase {memory.get('phase')})[/dim]"
|
||||
refl_line = format_reflection_surfacing(memory, for_explain=False)
|
||||
if refl_line:
|
||||
mem_line += f"\n[cyan]{refl_line}[/cyan]"
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold]Suggestion:[/bold]\n{llm_response.suggestion}\n\n"
|
||||
f"[dim]{llm_response.explanation}\n"
|
||||
f"Rationale: {llm_response.rationale}[/dim]",
|
||||
f"Rationale: {llm_response.rationale}{mem_line}[/dim]",
|
||||
title="LLM Response (via T04 seam)",
|
||||
border_style="magenta",
|
||||
padding=(1, 1),
|
||||
@@ -113,5 +268,205 @@ def handle_request(
|
||||
"[green]✓[/green] Request processed by orchestrator (T02+T03+T04 coordinated by T06)."
|
||||
)
|
||||
|
||||
return OrchestratorResult(
|
||||
user_request=user_request,
|
||||
assistant=llm_response.suggestion,
|
||||
explanation=llm_response.explanation,
|
||||
rationale=llm_response.rationale,
|
||||
context=ctx,
|
||||
memory=memory,
|
||||
risk=assessment.to_dict(),
|
||||
)
|
||||
|
||||
__all__ = ["handle_request"]
|
||||
|
||||
def run_retrospection(scope: str = ".", limit: int = 8) -> None:
|
||||
"""Guided retrospection session (T04 of CYA-WP-0003).
|
||||
|
||||
Helps the user review recent memory usage in the given scope,
|
||||
reflect, and record new interaction goals or preferences.
|
||||
These are stored using the retrospection-aware memory helper.
|
||||
"""
|
||||
console.print(
|
||||
Panel(
|
||||
"[bold cyan]Retrospection Session[/bold cyan]\n\n"
|
||||
f"Scope: [green]{scope}[/green]\n"
|
||||
"We will look at recent memory items and help you reflect.\n"
|
||||
"Your answers will be stored as durable retrospection memory.",
|
||||
title="cya retrospect",
|
||||
border_style="magenta",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
# Recall recent items, with bias toward retrospection kinds if present
|
||||
try:
|
||||
recent = recall_preferences(
|
||||
scope,
|
||||
limit=limit,
|
||||
kinds=[KIND_RETROSPECTION, KIND_INTERACTION_GOAL, "preference"],
|
||||
)
|
||||
items = recent.get("items", [])
|
||||
except Exception as e:
|
||||
console.print(f"[red]Could not load memory: {e}[/red]")
|
||||
return
|
||||
|
||||
if not items:
|
||||
console.print(
|
||||
"[yellow]No memory items found in this scope yet.[/yellow]\n"
|
||||
"You can create some with normal usage or explicit remembers."
|
||||
)
|
||||
return
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
"\n".join(
|
||||
f"• [bold]{item.get('key')}[/bold]: {item.get('value')}"
|
||||
for item in items[:5]
|
||||
),
|
||||
title=f"Recent Memory in {scope} (showing up to 5)",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
|
||||
# Simple guided reflection
|
||||
console.print("\n[bold]Reflection time[/bold]")
|
||||
|
||||
what_worked = typer.prompt(
|
||||
"What worked well in recent assistance? (short answer or 'skip')",
|
||||
default="",
|
||||
show_default=False,
|
||||
)
|
||||
if what_worked and what_worked.lower() not in ("skip", "s", ""):
|
||||
remember_retrospection_outcome(
|
||||
"what_worked", what_worked, scope=scope
|
||||
)
|
||||
console.print("[green]Recorded.[/green]")
|
||||
|
||||
what_to_change = typer.prompt(
|
||||
"What should change in future interactions? (e.g. 'be more concise', 'always show alternatives')",
|
||||
default="",
|
||||
show_default=False,
|
||||
)
|
||||
if what_to_change and what_to_change.lower() not in ("skip", "s", ""):
|
||||
remember_retrospection_outcome(
|
||||
"interaction_goal", what_to_change, scope=scope
|
||||
)
|
||||
console.print("[green]Recorded as interaction goal.[/green]")
|
||||
|
||||
safety_note = typer.prompt(
|
||||
"Any standing safety or preference rules for this project? (optional)",
|
||||
default="",
|
||||
show_default=False,
|
||||
)
|
||||
if safety_note and safety_note.lower() not in ("skip", "s", ""):
|
||||
remember_retrospection_outcome(
|
||||
"safety_preference", safety_note, scope=scope
|
||||
)
|
||||
console.print("[green]Recorded as safety preference.[/green]")
|
||||
|
||||
_capture_reflection_lessons(scope)
|
||||
_offer_reflection_compaction(scope)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
"Thank you. Your reflections have been stored as retrospection memory.\n"
|
||||
"They will be preferentially activated in future sessions in this scope.\n\n"
|
||||
"You can review them anytime with:\n"
|
||||
f" [bold]cya memory reflections --scope {scope}[/bold]\n"
|
||||
f" [bold]cya --explain-context \"...\"[/bold] (in this directory)\n"
|
||||
f" or inspect the JSON files in [cyan]~/.config/cya/memory/[/cyan]",
|
||||
title="Retrospection Complete",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _capture_reflection_lessons(scope: str) -> None:
|
||||
"""Profile 1: guided verbal lesson capture with preview and confirmation."""
|
||||
want = typer.prompt(
|
||||
"Capture 1–3 verbal lessons from this session? (y/n)",
|
||||
default="n",
|
||||
show_default=False,
|
||||
)
|
||||
if not want or want.strip().lower() in ("n", "no", "skip", "s", ""):
|
||||
return
|
||||
|
||||
answers: dict[str, str] = {}
|
||||
for prompt_key, label in REFLECTION_CAPTURE_PROMPTS:
|
||||
answers[prompt_key] = typer.prompt(label, default="", show_default=False)
|
||||
|
||||
lessons = collect_lessons_from_answers(answers)
|
||||
if not lessons:
|
||||
console.print("[yellow]No lessons captured — nothing stored.[/yellow]")
|
||||
return
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
preview_lessons(lessons),
|
||||
title="Preview — verbal lessons to save",
|
||||
border_style="cyan",
|
||||
)
|
||||
)
|
||||
confirm = typer.prompt("Save these lessons? (y/n)", default="n", show_default=False)
|
||||
if confirm.strip().lower() not in ("y", "yes"):
|
||||
console.print("[yellow]Lessons discarded — nothing stored.[/yellow]")
|
||||
return
|
||||
|
||||
count = save_reflection_lessons(
|
||||
lessons,
|
||||
scope,
|
||||
provenance=session_provenance(scope),
|
||||
)
|
||||
console.print(f"[green]Saved {count} verbal reflection(s) (Profile 1).[/green]")
|
||||
|
||||
|
||||
def _offer_reflection_compaction(scope: str) -> None:
|
||||
"""Offer explicit merge of near-duplicate reflections in this scope."""
|
||||
groups = find_duplicate_reflection_groups(scope)
|
||||
if not groups:
|
||||
return
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
f"Found {len(groups)} group(s) of similar reflections in scope [green]{scope}[/green].\n"
|
||||
"Compaction is opt-in — nothing is deleted without your confirmation.",
|
||||
title="Reflection compaction available",
|
||||
border_style="yellow",
|
||||
)
|
||||
)
|
||||
do_compact = typer.prompt("Review and compact duplicates? (y/n)", default="n", show_default=False)
|
||||
if do_compact.strip().lower() not in ("y", "yes"):
|
||||
return
|
||||
|
||||
for group in groups:
|
||||
console.print("\n[bold]Similar reflections:[/bold]")
|
||||
for item in group:
|
||||
console.print(f" • {item.get('key')}: {item.get('value')}")
|
||||
|
||||
keep = typer.prompt(
|
||||
"Key to keep (or 'skip' this group)",
|
||||
default=group[0].get("key", ""),
|
||||
show_default=True,
|
||||
)
|
||||
if not keep or keep.strip().lower() in ("skip", "s"):
|
||||
continue
|
||||
|
||||
remove_keys = [i.get("key") for i in group if i.get("key") != keep]
|
||||
merge = typer.prompt(
|
||||
"Merged text (Enter to keep existing value of kept key)",
|
||||
default="",
|
||||
show_default=False,
|
||||
)
|
||||
result = compact_reflections(
|
||||
scope,
|
||||
keep_key=keep,
|
||||
remove_keys=[k for k in remove_keys if k],
|
||||
merged_value=merge.strip() or None,
|
||||
)
|
||||
console.print(
|
||||
f"[green]Compacted: kept {result['kept']}, removed {len(result['removed'])}[/green]"
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["OrchestratorResult", "handle_request", "run_retrospection"]
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
"""Risk classification and mandatory confirmation layer (T03).
|
||||
"""Risk classification and mandatory confirmation layer (T03 + T04).
|
||||
|
||||
Genuine rule-based assessment is the *primary* mechanism (per operator
|
||||
direction recorded 2026-05-26 in Decision D1).
|
||||
|
||||
Memory signals (from phase-memory via recall_preferences) are considered
|
||||
as a secondary enrichment layer only (T04). They can add rationale or
|
||||
force extra caution, but **never** downgrade or remove mandatory
|
||||
confirmation for any non-SAFE level.
|
||||
|
||||
Results are designed to be surfaced to the LLM as structured context.
|
||||
The LLM may propose or refine suggestions, but any architecture-level,
|
||||
policy, or significant design decisions that surface during use must be
|
||||
@@ -11,7 +16,7 @@ captured as ADRs in this repository.
|
||||
This module is intentionally simple, deterministic, and fully inspectable.
|
||||
No ML, no external calls, no hidden state.
|
||||
|
||||
See workplan CYA-WP-0001-T03 for the full contract and acceptance criteria.
|
||||
See workplan CYA-WP-0001-T03 and CYA-WP-0002-T04.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -134,11 +139,20 @@ _RULES: list[tuple[re.Pattern, RiskLevel, str]] = [
|
||||
]
|
||||
|
||||
|
||||
def classify(request: str, context: Optional["ContextEnvelope"] = None) -> RiskAssessment:
|
||||
"""Primary rule-based risk classifier.
|
||||
def classify(
|
||||
request: str,
|
||||
context: Optional["ContextEnvelope"] = None,
|
||||
memory: dict | None = None,
|
||||
) -> RiskAssessment:
|
||||
"""Primary rule-based risk classifier (T03 core + T04 memory signals).
|
||||
|
||||
Returns the highest-severity matching assessment.
|
||||
Always produces a result; never raises for bad input.
|
||||
|
||||
memory (optional): output dict from recall_preferences (T02/T03).
|
||||
Memory signals are used *only* to enrich rationale or force extra
|
||||
caution. They are explicitly forbidden from downgrading any
|
||||
non-SAFE level or clearing requires_confirmation.
|
||||
"""
|
||||
if not request or not request.strip():
|
||||
return RiskAssessment(
|
||||
@@ -168,7 +182,7 @@ def classify(request: str, context: Optional["ContextEnvelope"] = None) -> RiskA
|
||||
preview = _build_preview(text, chosen_level, context)
|
||||
affected = _build_affected_summary(context) if context else None
|
||||
|
||||
return RiskAssessment(
|
||||
assessment = RiskAssessment(
|
||||
level=chosen_level,
|
||||
rationale=chosen_rationale,
|
||||
rules_triggered=triggered or ["No specific high-risk rule matched."],
|
||||
@@ -178,6 +192,12 @@ def classify(request: str, context: Optional["ContextEnvelope"] = None) -> RiskA
|
||||
confidence=0.85 if triggered else 0.6,
|
||||
)
|
||||
|
||||
# T04: memory signal enrichment (conservative only)
|
||||
if memory:
|
||||
assessment = _apply_memory_signals(assessment, memory, text)
|
||||
|
||||
return assessment
|
||||
|
||||
|
||||
def _severity(level: RiskLevel) -> int:
|
||||
order = {
|
||||
@@ -216,6 +236,62 @@ def _build_affected_summary(context: Optional["ContextEnvelope"]) -> str | None:
|
||||
return f"Working in: {context.cwd}. Visible top-level items: {', '.join(top)}"
|
||||
|
||||
|
||||
def _apply_memory_signals(
|
||||
assessment: RiskAssessment,
|
||||
memory: dict,
|
||||
request_text: str,
|
||||
) -> RiskAssessment:
|
||||
"""
|
||||
T04: Conservative memory signal enrichment.
|
||||
|
||||
Memory can:
|
||||
- Add explanatory notes to rationale for remembered "approved" patterns.
|
||||
- Force requires_confirmation=True (and append rationale) when a
|
||||
"never auto-run" / "dangerous" preference matches the request.
|
||||
|
||||
Memory is **never** allowed to:
|
||||
- Downgrade a non-SAFE level.
|
||||
- Clear requires_confirmation once it is True.
|
||||
- Turn a rule-matched destructive command into "safe".
|
||||
"""
|
||||
items = memory.get("items", []) if isinstance(memory, dict) else []
|
||||
if not items:
|
||||
return assessment
|
||||
|
||||
lowered_request = request_text.lower()
|
||||
memory_notes: list[str] = []
|
||||
force_confirm = False
|
||||
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
key = str(item.get("key", "")).lower()
|
||||
value = str(item.get("value", "")).lower()
|
||||
|
||||
# "never auto-run" style standing preferences
|
||||
if any(kw in key for kw in ("never", "no-auto", "never-auto", "dangerous", "block")):
|
||||
if any(kw in lowered_request for kw in (value, key)) or value in lowered_request:
|
||||
force_confirm = True
|
||||
memory_notes.append(f"Memory preference: '{item.get('key')}' matches request")
|
||||
|
||||
# Positive "approved" / safe-pattern memory (only informational)
|
||||
if any(kw in key for kw in ("approved", "safe", "whitelist", "allow")):
|
||||
if value and value in lowered_request:
|
||||
memory_notes.append(f"Memory note: previously approved pattern '{item.get('key')}'")
|
||||
|
||||
if memory_notes:
|
||||
extra = " | Memory signals: " + "; ".join(memory_notes)
|
||||
assessment.rationale = (assessment.rationale or "") + extra
|
||||
assessment.rules_triggered.append("Memory signal considered (T04)")
|
||||
|
||||
if force_confirm and not assessment.requires_confirmation:
|
||||
assessment.requires_confirmation = True
|
||||
assessment.rationale += " (forced by memory 'never' preference)"
|
||||
assessment.rules_triggered.append("Memory-enforced confirmation")
|
||||
|
||||
return assessment
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Mandatory confirmation (always in the launching terminal)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
862
src/cya/shell_session.py
Normal file
862
src/cya/shell_session.py
Normal file
@@ -0,0 +1,862 @@
|
||||
"""Interactive shell session runtime for ``cya shell`` (CYA-WP-0007).
|
||||
|
||||
The shell is a thin interactive client for the existing orchestrator. It owns
|
||||
session-local state, optional history context, State Hub orientation, slash
|
||||
commands, JSONL persistence, and user-triggered learning/export flows.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import sys
|
||||
import uuid
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
from urllib import parse, request
|
||||
|
||||
import typer
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
from cya.config import ShellHistorySettings, load_shell_history_settings
|
||||
from cya.memory import KIND_SESSION_TURN, remember_preference
|
||||
from cya.memory.reflections import (
|
||||
REFLECTION_CAPTURE_PROMPTS,
|
||||
collect_lessons_from_answers,
|
||||
preview_lessons,
|
||||
save_reflection_lessons,
|
||||
)
|
||||
from cya.orchestrator import OrchestratorResult, handle_request
|
||||
|
||||
TOPIC_ID = "64418556-3206-457a-ba29-6884b5b12cf3"
|
||||
DEFAULT_AGENT = "can-you-assist"
|
||||
DEFAULT_HUB_URL = os.environ.get("CYA_STATE_HUB_URL", "http://127.0.0.1:8000")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ShellHistoryContext:
|
||||
enabled: bool
|
||||
source: str
|
||||
limit: int
|
||||
histfile: str | None = None
|
||||
lines: list[dict[str, Any]] = field(default_factory=list)
|
||||
redactions: int = 0
|
||||
notes: list[str] = field(default_factory=list)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HubOrientation:
|
||||
base_url: str
|
||||
topic_id: str
|
||||
agent: str
|
||||
brief: str | None = None
|
||||
active_workstreams: list[dict[str, Any]] = field(default_factory=list)
|
||||
inbox: list[dict[str, Any]] = field(default_factory=list)
|
||||
errors: list[str] = field(default_factory=list)
|
||||
remote_checked: bool = False
|
||||
|
||||
@property
|
||||
def hub_available(self) -> bool:
|
||||
return self.remote_checked and not self.errors
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
data = asdict(self)
|
||||
data["hub_available"] = self.hub_available
|
||||
return data
|
||||
|
||||
def compact(self) -> dict[str, Any]:
|
||||
return {
|
||||
"base_url": self.base_url,
|
||||
"topic_id": self.topic_id,
|
||||
"agent": self.agent,
|
||||
"hub_available": self.hub_available,
|
||||
"active_workstream_count": len(self.active_workstreams),
|
||||
"inbox_count": len(self.inbox),
|
||||
"errors": self.errors,
|
||||
"workstreams": [
|
||||
{
|
||||
"id": w.get("id") or w.get("workstream_id"),
|
||||
"title": w.get("title") or w.get("name"),
|
||||
"status": w.get("status"),
|
||||
}
|
||||
for w in self.active_workstreams[:5]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
_SECRET_PATTERNS: tuple[re.Pattern[str], ...] = (
|
||||
re.compile(
|
||||
r"(?i)\b([A-Z0-9_]*(?:API[_-]?KEY|TOKEN|PASSWORD|PASSWD|SECRET)[A-Z0-9_]*)"
|
||||
r"(\s*=\s*)([^\s;]+)"
|
||||
),
|
||||
re.compile(r"(?i)\b(--(?:api-key|token|password|secret)\s+)([^\s;]+)"),
|
||||
re.compile(r"\b(sk-[A-Za-z0-9_-]{8,})\b"),
|
||||
re.compile(r"\b(gh[pousr]_[A-Za-z0-9_]{12,})\b"),
|
||||
re.compile(r"\b(xox[baprs]-[A-Za-z0-9-]{12,})\b"),
|
||||
re.compile(r"\b(AKIA[0-9A-Z]{12,})\b"),
|
||||
)
|
||||
|
||||
|
||||
InputFunc = Callable[[str], str]
|
||||
|
||||
|
||||
def utc_now() -> str:
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
|
||||
def make_session_id() -> str:
|
||||
stamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
|
||||
return f"cya-{stamp}-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
|
||||
def session_root() -> Path:
|
||||
root = Path.home() / ".config" / "cya" / "sessions"
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
return root
|
||||
|
||||
|
||||
def default_session_path(session_id: str) -> Path:
|
||||
return session_root() / f"{session_id}.jsonl"
|
||||
|
||||
|
||||
def redact_secrets(text: str) -> tuple[str, int]:
|
||||
redacted = text
|
||||
count = 0
|
||||
|
||||
def _replace(match: re.Match[str]) -> str:
|
||||
groups = match.groups()
|
||||
if len(groups) >= 3:
|
||||
return f"{groups[0]}{groups[1]}[REDACTED]"
|
||||
return "[REDACTED_SECRET]"
|
||||
|
||||
for pattern in _SECRET_PATTERNS:
|
||||
redacted, n = pattern.subn(_replace, redacted)
|
||||
count += n
|
||||
return redacted, count
|
||||
|
||||
|
||||
def _normalize_history_line(line: str) -> str:
|
||||
text = line.strip()
|
||||
if text.startswith(": ") and ";" in text:
|
||||
return text.split(";", 1)[1].strip()
|
||||
if text.startswith("- cmd: "):
|
||||
return text.removeprefix("- cmd: ").strip()
|
||||
return text
|
||||
|
||||
|
||||
def _default_histfile(env: dict[str, str], home: Path) -> Path | None:
|
||||
explicit = env.get("HISTFILE")
|
||||
if explicit:
|
||||
return Path(explicit).expanduser()
|
||||
for candidate in (
|
||||
home / ".bash_history",
|
||||
home / ".zsh_history",
|
||||
home / ".local" / "share" / "fish" / "fish_history",
|
||||
):
|
||||
if candidate.is_file():
|
||||
return candidate
|
||||
return None
|
||||
|
||||
|
||||
def collect_shell_history(
|
||||
settings: ShellHistorySettings,
|
||||
*,
|
||||
env: dict[str, str] | None = None,
|
||||
home: Path | None = None,
|
||||
) -> ShellHistoryContext:
|
||||
env = env or os.environ
|
||||
home = home or Path.home()
|
||||
ctx = ShellHistoryContext(
|
||||
enabled=settings.enabled,
|
||||
source=settings.source,
|
||||
limit=settings.limit,
|
||||
histfile=settings.histfile,
|
||||
notes=list(settings.notes),
|
||||
)
|
||||
|
||||
if not settings.enabled:
|
||||
ctx.notes.append("Shell history disabled; no history was collected.")
|
||||
return ctx
|
||||
if settings.limit <= 0:
|
||||
ctx.notes.append("Shell history enabled with limit 0; no lines collected.")
|
||||
return ctx
|
||||
|
||||
histfile = Path(settings.histfile).expanduser() if settings.histfile else _default_histfile(env, home)
|
||||
if not histfile:
|
||||
ctx.notes.append("No shell history file found from HISTFILE or known fallbacks.")
|
||||
return ctx
|
||||
|
||||
ctx.histfile = str(histfile)
|
||||
try:
|
||||
raw = histfile.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
except OSError as exc:
|
||||
ctx.notes.append(f"Could not read history file: {exc}")
|
||||
return ctx
|
||||
|
||||
normalized = [_normalize_history_line(line) for line in raw]
|
||||
normalized = [line for line in normalized if line]
|
||||
start = max(0, len(normalized) - settings.limit)
|
||||
for offset, line in enumerate(normalized[start:], start=start + 1):
|
||||
clean, count = redact_secrets(line[:1000])
|
||||
ctx.redactions += count
|
||||
ctx.lines.append(
|
||||
{
|
||||
"line_number": offset,
|
||||
"command": clean,
|
||||
"source": str(histfile),
|
||||
"provenance": "shell_history.histfile",
|
||||
}
|
||||
)
|
||||
if ctx.redactions:
|
||||
ctx.notes.append(f"Redacted {ctx.redactions} secret-like value(s).")
|
||||
return ctx
|
||||
|
||||
|
||||
def render_shell_history_explanation(history: ShellHistoryContext) -> str:
|
||||
lines = [
|
||||
f"enabled: {history.enabled}",
|
||||
f"source: {history.source}",
|
||||
f"limit: {history.limit}",
|
||||
f"histfile: {history.histfile or '(none)'}",
|
||||
f"lines: {len(history.lines)}",
|
||||
]
|
||||
if history.notes:
|
||||
lines.append("notes:")
|
||||
lines.extend(f" - {note}" for note in history.notes)
|
||||
if history.lines:
|
||||
lines.append("commands:")
|
||||
for item in history.lines:
|
||||
lines.append(
|
||||
f" {item['line_number']}: {item['command']} ({item['provenance']})"
|
||||
)
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _read_json_url(url: str, *, timeout: float) -> Any:
|
||||
req = request.Request(url, headers={"User-Agent": "cya-shell/0.1"})
|
||||
with request.urlopen(req, timeout=timeout) as resp:
|
||||
data = resp.read().decode("utf-8")
|
||||
if not data.strip():
|
||||
return []
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def _post_json_url(url: str, payload: dict[str, Any], *, timeout: float = 3.0) -> Any:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
req = request.Request(
|
||||
url,
|
||||
data=body,
|
||||
method="POST",
|
||||
headers={"Content-Type": "application/json", "User-Agent": "cya-shell/0.1"},
|
||||
)
|
||||
with request.urlopen(req, timeout=timeout) as resp:
|
||||
data = resp.read().decode("utf-8")
|
||||
if not data.strip():
|
||||
return {}
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def _patch_json_url(url: str, payload: dict[str, Any], *, timeout: float = 3.0) -> Any:
|
||||
body = json.dumps(payload).encode("utf-8")
|
||||
req = request.Request(
|
||||
url,
|
||||
data=body,
|
||||
method="PATCH",
|
||||
headers={"Content-Type": "application/json", "User-Agent": "cya-shell/0.1"},
|
||||
)
|
||||
with request.urlopen(req, timeout=timeout) as resp:
|
||||
data = resp.read().decode("utf-8")
|
||||
if not data.strip():
|
||||
return {}
|
||||
return json.loads(data)
|
||||
|
||||
|
||||
def _as_list(value: Any) -> list[dict[str, Any]]:
|
||||
if isinstance(value, list):
|
||||
return [item for item in value if isinstance(item, dict)]
|
||||
if isinstance(value, dict):
|
||||
for key in ("items", "results", "data"):
|
||||
if isinstance(value.get(key), list):
|
||||
return [item for item in value[key] if isinstance(item, dict)]
|
||||
return []
|
||||
|
||||
|
||||
def load_hub_orientation(
|
||||
*,
|
||||
cwd: Path | None = None,
|
||||
base_url: str = DEFAULT_HUB_URL,
|
||||
topic_id: str = TOPIC_ID,
|
||||
agent: str = DEFAULT_AGENT,
|
||||
timeout: float = 1.5,
|
||||
include_remote: bool = True,
|
||||
) -> HubOrientation:
|
||||
cwd = cwd or Path.cwd()
|
||||
orientation = HubOrientation(base_url=base_url.rstrip("/"), topic_id=topic_id, agent=agent)
|
||||
|
||||
brief = cwd / ".custodian-brief.md"
|
||||
if brief.is_file():
|
||||
try:
|
||||
orientation.brief = brief.read_text(encoding="utf-8", errors="replace")[:6000]
|
||||
except OSError as exc:
|
||||
orientation.errors.append(f"brief read failed: {exc}")
|
||||
|
||||
if not include_remote:
|
||||
orientation.errors.append("State Hub remote orientation skipped by --no-hub.")
|
||||
return orientation
|
||||
|
||||
try:
|
||||
qs = parse.urlencode({"topic_id": topic_id, "status": "active"})
|
||||
data = _read_json_url(f"{orientation.base_url}/workstreams/?{qs}", timeout=timeout)
|
||||
orientation.active_workstreams = _as_list(data)
|
||||
orientation.remote_checked = True
|
||||
except Exception as exc:
|
||||
orientation.errors.append(f"workstreams unavailable: {exc}")
|
||||
|
||||
try:
|
||||
qs = parse.urlencode({"to_agent": agent, "unread_only": "true"})
|
||||
data = _read_json_url(f"{orientation.base_url}/messages/?{qs}", timeout=timeout)
|
||||
orientation.inbox = _as_list(data)
|
||||
orientation.remote_checked = True
|
||||
except Exception as exc:
|
||||
orientation.errors.append(f"inbox unavailable: {exc}")
|
||||
|
||||
return orientation
|
||||
|
||||
|
||||
def render_hub_summary(orientation: HubOrientation) -> str:
|
||||
lines: list[str] = []
|
||||
if orientation.brief:
|
||||
for line in orientation.brief.splitlines():
|
||||
clean = line.strip("# ").strip()
|
||||
if clean:
|
||||
lines.append(clean)
|
||||
break
|
||||
if orientation.remote_checked:
|
||||
lines.append(f"Active workstreams: {len(orientation.active_workstreams)}")
|
||||
lines.append(f"Unread inbox messages: {len(orientation.inbox)}")
|
||||
else:
|
||||
lines.append("State Hub remote orientation not available.")
|
||||
if orientation.errors:
|
||||
lines.append("Notes: " + "; ".join(orientation.errors[:2]))
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def render_hub_details(orientation: HubOrientation) -> str:
|
||||
lines = [render_hub_summary(orientation), ""]
|
||||
if orientation.active_workstreams:
|
||||
lines.append("Workstreams:")
|
||||
for ws in orientation.active_workstreams[:8]:
|
||||
title = ws.get("title") or ws.get("name") or "(untitled)"
|
||||
ident = ws.get("id") or ws.get("workstream_id") or "?"
|
||||
status = ws.get("status") or "?"
|
||||
lines.append(f" - {title} [{status}] {ident}")
|
||||
tasks = ws.get("tasks") or ws.get("open_tasks") or []
|
||||
if isinstance(tasks, list):
|
||||
for task in tasks[:5]:
|
||||
if isinstance(task, dict):
|
||||
t_title = task.get("title") or task.get("name") or "(task)"
|
||||
t_status = task.get("status") or "?"
|
||||
lines.append(f" * {t_title} [{t_status}]")
|
||||
if orientation.inbox:
|
||||
lines.append("\nInbox:")
|
||||
for msg in orientation.inbox[:10]:
|
||||
title = msg.get("title") or msg.get("summary") or msg.get("subject") or "(message)"
|
||||
ident = msg.get("id") or "?"
|
||||
lines.append(f" - {ident}: {title}")
|
||||
if orientation.errors:
|
||||
lines.append("\nErrors:")
|
||||
lines.extend(f" - {err}" for err in orientation.errors)
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
|
||||
def post_progress(summary: str, orientation: HubOrientation) -> dict[str, Any]:
|
||||
return _post_json_url(
|
||||
f"{orientation.base_url}/progress/",
|
||||
{"summary": summary, "event_type": "note", "author": "cya-shell"},
|
||||
)
|
||||
|
||||
|
||||
def mark_message_read(message_id: str, orientation: HubOrientation) -> dict[str, Any]:
|
||||
return _patch_json_url(f"{orientation.base_url}/messages/{message_id}/read", {})
|
||||
|
||||
|
||||
def detect_weakness_hints(
|
||||
user_text: str,
|
||||
assistant_text: str = "",
|
||||
*,
|
||||
risk: dict[str, Any] | None = None,
|
||||
turn_history: list[dict[str, Any]] | None = None,
|
||||
hub: HubOrientation | None = None,
|
||||
) -> list[dict[str, str]]:
|
||||
text = f"{user_text}\n{assistant_text}".lower()
|
||||
risk = risk or {}
|
||||
turn_history = turn_history or []
|
||||
hints: list[dict[str, str]] = []
|
||||
|
||||
secret_terms = ("api key", "token", "password", "secret", "openrouter", "ops_hub_key")
|
||||
if any(term in text for term in secret_terms) and "warden route" not in text:
|
||||
hints.append(
|
||||
{
|
||||
"rule": "credential-routing",
|
||||
"title": "Credential routing",
|
||||
"message": "Route secret custody first with warden route/OpenBao; do not paste keys into prompts, Git, State Hub, or logs.",
|
||||
}
|
||||
)
|
||||
|
||||
if "ops-warden" in text and any(term in text for term in secret_terms):
|
||||
hints.append(
|
||||
{
|
||||
"rule": "ops-warden-secret-anti-pattern",
|
||||
"title": "ops-warden boundary",
|
||||
"message": "ops-warden issues SSH certificates only; API keys and passwords belong to their routed owner.",
|
||||
}
|
||||
)
|
||||
|
||||
if re.search(r"\b(curl|wget)\b.*\|\s*(bash|sh|zsh|python)", text, re.S):
|
||||
hints.append(
|
||||
{
|
||||
"rule": "remote-exec-review",
|
||||
"title": "Remote execution review",
|
||||
"message": "Inspect downloaded content before execution and keep the safety confirmation gate intact.",
|
||||
}
|
||||
)
|
||||
|
||||
level = str(risk.get("level", "")).lower()
|
||||
previous_destructive = any(
|
||||
str(turn.get("risk", "")).lower() in {"destructive", "mass_edit"}
|
||||
for turn in turn_history[-3:]
|
||||
)
|
||||
if level in {"destructive", "mass_edit"} and previous_destructive:
|
||||
hints.append(
|
||||
{
|
||||
"rule": "repeated-destructive-intent",
|
||||
"title": "Repeated destructive intent",
|
||||
"message": "Repeated destructive or mass-edit turns are a cue to slow down and verify scope before proceeding.",
|
||||
}
|
||||
)
|
||||
|
||||
wp_tokens = set(re.findall(r"\b[A-Z]{2,5}-WP-\d{4}\b", user_text.upper()))
|
||||
if wp_tokens and hub is not None:
|
||||
haystack = json.dumps(hub.compact(), default=str).upper()
|
||||
missing = sorted(token for token in wp_tokens if token not in haystack)
|
||||
if missing:
|
||||
hints.append(
|
||||
{
|
||||
"rule": "state-hub-alignment",
|
||||
"title": "State Hub alignment",
|
||||
"message": f"{', '.join(missing)} was not visible in the loaded hub summary; confirm the local workplan file is the source of truth.",
|
||||
}
|
||||
)
|
||||
|
||||
return hints
|
||||
|
||||
|
||||
def render_weakness_hints(hints: list[dict[str, str]]) -> str:
|
||||
lines = []
|
||||
for hint in hints:
|
||||
lines.append(f"[{hint['rule']}] {hint['message']}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class ShellSession:
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
session_id: str | None = None,
|
||||
offline: bool = False,
|
||||
history: ShellHistoryContext | None = None,
|
||||
hub: HubOrientation | None = None,
|
||||
console: Console | None = None,
|
||||
input_func: InputFunc | None = None,
|
||||
interactive: bool | None = None,
|
||||
offer_end_learning: bool = True,
|
||||
) -> None:
|
||||
self.session_id = session_id or make_session_id()
|
||||
self.path = default_session_path(self.session_id)
|
||||
self.offline = offline
|
||||
self.history = history or ShellHistoryContext(False, "default", 0)
|
||||
self.hub = hub or HubOrientation(DEFAULT_HUB_URL, TOPIC_ID, DEFAULT_AGENT)
|
||||
self.console = console or Console()
|
||||
self.input_func = input_func or input
|
||||
self.interactive = sys.stdin.isatty() if interactive is None else interactive
|
||||
self.offer_end_learning = offer_end_learning
|
||||
self.turns: list[dict[str, Any]] = []
|
||||
self.last_result: OrchestratorResult | None = None
|
||||
self.last_hints: list[dict[str, str]] = []
|
||||
self._started = False
|
||||
|
||||
def append_event(self, event: dict[str, Any]) -> None:
|
||||
self.path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with self.path.open("a", encoding="utf-8") as fh:
|
||||
fh.write(json.dumps(event, default=str) + "\n")
|
||||
|
||||
def start(self) -> None:
|
||||
if self._started:
|
||||
return
|
||||
self._started = True
|
||||
self.append_event(
|
||||
{
|
||||
"kind": "session_start",
|
||||
"session_id": self.session_id,
|
||||
"created_at": utc_now(),
|
||||
"cwd": str(Path.cwd()),
|
||||
"history": self.history.to_dict(),
|
||||
"hub": self.hub.compact(),
|
||||
}
|
||||
)
|
||||
body = (
|
||||
f"Session: {self.session_id}\n"
|
||||
f"Artifact: {self.path}\n\n"
|
||||
f"{render_hub_summary(self.hub)}\n\n"
|
||||
f"History lines: {len(self.history.lines)}"
|
||||
)
|
||||
self.console.print(Panel(body, title="cya shell", border_style="cyan"))
|
||||
self.console.print("Type /help for commands, /exit to finish.")
|
||||
try:
|
||||
import readline # noqa: F401
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def run(self) -> Path:
|
||||
self.start()
|
||||
while True:
|
||||
try:
|
||||
line = self.input_func("cya> ")
|
||||
except KeyboardInterrupt:
|
||||
self.console.print("[yellow]Interrupted. Use /exit to end the session.[/yellow]")
|
||||
continue
|
||||
except EOFError:
|
||||
self.console.print("[dim]EOF received; ending session.[/dim]")
|
||||
break
|
||||
|
||||
text = line.strip()
|
||||
if not text:
|
||||
continue
|
||||
if text.startswith("/"):
|
||||
keep_going = self.handle_command(text)
|
||||
if not keep_going:
|
||||
break
|
||||
continue
|
||||
self.handle_turn(text)
|
||||
|
||||
self.close()
|
||||
return self.path
|
||||
|
||||
def handle_turn(self, text: str) -> OrchestratorResult:
|
||||
extra_context = {
|
||||
"session_id": self.session_id,
|
||||
"session_path": str(self.path),
|
||||
"shell_history": self.history.to_dict(),
|
||||
"hub_orientation": self.hub.compact(),
|
||||
}
|
||||
result = handle_request(
|
||||
text,
|
||||
offline=self.offline,
|
||||
session_turns=list(self.turns),
|
||||
extra_context=extra_context,
|
||||
)
|
||||
hints = detect_weakness_hints(
|
||||
text,
|
||||
result.assistant,
|
||||
risk=result.risk,
|
||||
turn_history=self.turns,
|
||||
hub=self.hub,
|
||||
)
|
||||
if hints:
|
||||
self.console.print(
|
||||
Panel(render_weakness_hints(hints), title="Weakness Hints", border_style="yellow")
|
||||
)
|
||||
|
||||
clean_user, red_user = redact_secrets(text)
|
||||
clean_assistant, red_assistant = redact_secrets(result.assistant)
|
||||
risk_level = result.risk.get("level", "safe")
|
||||
turn = {
|
||||
"user": text,
|
||||
"assistant": result.assistant,
|
||||
"risk": risk_level,
|
||||
}
|
||||
self.turns.append(turn)
|
||||
self.last_result = result
|
||||
self.last_hints = hints
|
||||
self.append_event(
|
||||
{
|
||||
"kind": KIND_SESSION_TURN,
|
||||
"session_id": self.session_id,
|
||||
"turn_index": len(self.turns),
|
||||
"created_at": utc_now(),
|
||||
"user": clean_user,
|
||||
"assistant": clean_assistant,
|
||||
"risk": result.risk,
|
||||
"cancelled": result.cancelled,
|
||||
"dry_run": result.dry_run,
|
||||
"weakness_hints": hints,
|
||||
"redactions": red_user + red_assistant,
|
||||
}
|
||||
)
|
||||
return result
|
||||
|
||||
def handle_command(self, text: str) -> bool:
|
||||
try:
|
||||
parts = shlex.split(text)
|
||||
except ValueError as exc:
|
||||
self.console.print(f"[red]Could not parse command: {exc}[/red]")
|
||||
return True
|
||||
if not parts:
|
||||
return True
|
||||
|
||||
cmd = parts[0].lower()
|
||||
if cmd in {"/exit", "/quit"}:
|
||||
return False
|
||||
if cmd == "/help":
|
||||
self.show_help()
|
||||
return True
|
||||
if cmd == "/explain":
|
||||
self.show_explain()
|
||||
return True
|
||||
if cmd == "/hub":
|
||||
self.handle_hub(parts)
|
||||
return True
|
||||
if cmd == "/inbox":
|
||||
self.handle_inbox(parts)
|
||||
return True
|
||||
if cmd == "/export-session":
|
||||
self.export_session()
|
||||
return True
|
||||
if cmd == "/learn":
|
||||
self.capture_reflections()
|
||||
return True
|
||||
|
||||
self.console.print(f"[yellow]Unknown command {parts[0]!r}. Try /help.[/yellow]")
|
||||
return True
|
||||
|
||||
def show_help(self) -> None:
|
||||
self.console.print(
|
||||
Panel(
|
||||
"\n".join(
|
||||
[
|
||||
"/help - show commands",
|
||||
"/explain - show context, risk, history, hub, and hints for the last turn",
|
||||
"/hub - show active State Hub workstreams and tasks",
|
||||
"/hub log \"summary\" - post a progress note after confirmation",
|
||||
"/inbox - show unread State Hub messages",
|
||||
"/inbox read <id> - explicitly mark a message read",
|
||||
"/export-session - write a redacted session summary JSON",
|
||||
"/learn - capture Profile 1 reflections now",
|
||||
"/exit - close the shell session",
|
||||
]
|
||||
),
|
||||
title="cya shell commands",
|
||||
border_style="cyan",
|
||||
)
|
||||
)
|
||||
|
||||
def show_explain(self) -> None:
|
||||
if not self.last_result:
|
||||
self.console.print("[yellow]No turns yet.[/yellow]")
|
||||
self.console.print(
|
||||
Panel(
|
||||
render_shell_history_explanation(self.history),
|
||||
title="Shell History Context",
|
||||
border_style="cyan",
|
||||
)
|
||||
)
|
||||
return
|
||||
payload = {
|
||||
"session_id": self.session_id,
|
||||
"risk": self.last_result.risk,
|
||||
"context": self.last_result.context,
|
||||
"weakness_hints": self.last_hints,
|
||||
}
|
||||
self.console.print(
|
||||
Panel(
|
||||
json.dumps(payload, indent=2, default=str),
|
||||
title="Last Turn Explanation",
|
||||
border_style="green",
|
||||
)
|
||||
)
|
||||
|
||||
def handle_hub(self, parts: list[str]) -> None:
|
||||
if len(parts) == 1:
|
||||
self.console.print(Panel(render_hub_details(self.hub), title="State Hub"))
|
||||
return
|
||||
if len(parts) >= 3 and parts[1].lower() == "log":
|
||||
summary = " ".join(parts[2:]).strip()
|
||||
if not summary:
|
||||
self.console.print("[yellow]Usage: /hub log \"summary\"[/yellow]")
|
||||
return
|
||||
if not self.interactive:
|
||||
self.console.print("[yellow]Hub writes require an interactive confirmation.[/yellow]")
|
||||
return
|
||||
if not typer.confirm(f"Post progress note to {self.hub.base_url}?", default=False):
|
||||
self.console.print("[yellow]Progress note not posted.[/yellow]")
|
||||
return
|
||||
try:
|
||||
result = post_progress(summary, self.hub)
|
||||
self.console.print(f"[green]Posted progress note.[/green] {result}")
|
||||
except Exception as exc:
|
||||
self.console.print(f"[red]Progress post failed: {exc}[/red]")
|
||||
return
|
||||
self.console.print("[yellow]Usage: /hub or /hub log \"summary\"[/yellow]")
|
||||
|
||||
def handle_inbox(self, parts: list[str]) -> None:
|
||||
if len(parts) == 1:
|
||||
body = "No unread messages." if not self.hub.inbox else render_hub_details(self.hub)
|
||||
self.console.print(Panel(body, title="State Hub Inbox"))
|
||||
return
|
||||
if len(parts) == 3 and parts[1].lower() == "read":
|
||||
message_id = parts[2]
|
||||
if not self.interactive:
|
||||
self.console.print("[yellow]Mark-read requires an interactive session.[/yellow]")
|
||||
return
|
||||
try:
|
||||
result = mark_message_read(message_id, self.hub)
|
||||
self.console.print(f"[green]Marked message read.[/green] {result}")
|
||||
except Exception as exc:
|
||||
self.console.print(f"[red]Mark-read failed: {exc}[/red]")
|
||||
return
|
||||
self.console.print("[yellow]Usage: /inbox or /inbox read <id>[/yellow]")
|
||||
|
||||
def export_session(self) -> Path:
|
||||
out = self.path.with_name(f"{self.session_id}-summary.json")
|
||||
turns = []
|
||||
for i, turn in enumerate(self.turns, 1):
|
||||
user, user_redactions = redact_secrets(str(turn.get("user", "")))
|
||||
assistant, assistant_redactions = redact_secrets(str(turn.get("assistant", "")))
|
||||
turns.append(
|
||||
{
|
||||
"turn_index": i,
|
||||
"user": user,
|
||||
"assistant": assistant,
|
||||
"risk": turn.get("risk"),
|
||||
"redactions": user_redactions + assistant_redactions,
|
||||
}
|
||||
)
|
||||
payload = {
|
||||
"kind": "session_export",
|
||||
"session_id": self.session_id,
|
||||
"exported_at": utc_now(),
|
||||
"session_path": str(self.path),
|
||||
"turn_count": len(turns),
|
||||
"turns": turns,
|
||||
"history": self.history.to_dict(),
|
||||
"hub": self.hub.compact(),
|
||||
}
|
||||
out.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
|
||||
self.console.print(f"[green]Exported session summary:[/green] {out}")
|
||||
self.append_event(
|
||||
{"kind": "session_export", "session_id": self.session_id, "path": str(out), "created_at": utc_now()}
|
||||
)
|
||||
return out
|
||||
|
||||
def capture_reflections(self) -> int:
|
||||
if not self.interactive:
|
||||
self.console.print("[yellow]Reflection capture needs an interactive terminal.[/yellow]")
|
||||
return 0
|
||||
answers: dict[str, str] = {}
|
||||
for prompt_key, label in REFLECTION_CAPTURE_PROMPTS:
|
||||
answers[prompt_key] = typer.prompt(label, default="", show_default=False)
|
||||
lessons = collect_lessons_from_answers(answers)
|
||||
if not lessons:
|
||||
self.console.print("[yellow]No lessons captured.[/yellow]")
|
||||
return 0
|
||||
self.console.print(Panel(preview_lessons(lessons), title="Preview lessons"))
|
||||
if not typer.confirm("Save these lessons?", default=False):
|
||||
self.console.print("[yellow]Lessons discarded.[/yellow]")
|
||||
return 0
|
||||
count = save_reflection_lessons(
|
||||
lessons,
|
||||
".",
|
||||
provenance={
|
||||
"session_date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
||||
"scope": ".",
|
||||
"source": "cya shell",
|
||||
"session_id": self.session_id,
|
||||
},
|
||||
)
|
||||
self.console.print(f"[green]Saved {count} reflection(s).[/green]")
|
||||
return count
|
||||
|
||||
def remember_turns(self) -> int:
|
||||
count = 0
|
||||
for i, turn in enumerate(self.turns, 1):
|
||||
user, _ = redact_secrets(str(turn.get("user", "")))
|
||||
assistant, _ = redact_secrets(str(turn.get("assistant", "")))
|
||||
remember_preference(
|
||||
f"session_turn_{self.session_id}_{i}",
|
||||
{"user": user, "assistant": assistant, "risk": turn.get("risk")},
|
||||
scope=".",
|
||||
kind=KIND_SESSION_TURN,
|
||||
provenance={"source": "cya shell", "session_id": self.session_id, "turn_index": i},
|
||||
)
|
||||
count += 1
|
||||
return count
|
||||
|
||||
def close(self) -> None:
|
||||
self.append_event(
|
||||
{
|
||||
"kind": "session_end",
|
||||
"session_id": self.session_id,
|
||||
"ended_at": utc_now(),
|
||||
"turn_count": len(self.turns),
|
||||
}
|
||||
)
|
||||
if self.interactive and self.offer_end_learning and self.turns:
|
||||
if typer.confirm("Capture Profile 1 lessons from this shell session?", default=False):
|
||||
self.capture_reflections()
|
||||
if typer.confirm("Store redacted session turns as session_turn memory?", default=False):
|
||||
count = self.remember_turns()
|
||||
self.console.print(f"[green]Stored {count} session turn(s).[/green]")
|
||||
self.console.print(f"[green]Session saved:[/green] {self.path}")
|
||||
|
||||
|
||||
def run_shell(
|
||||
*,
|
||||
with_history: bool = False,
|
||||
history_limit: int | None = None,
|
||||
offline: bool = False,
|
||||
hub_url: str = DEFAULT_HUB_URL,
|
||||
no_hub: bool = False,
|
||||
session_id: str | None = None,
|
||||
console: Console | None = None,
|
||||
input_func: InputFunc | None = None,
|
||||
interactive: bool | None = None,
|
||||
offer_end_learning: bool = True,
|
||||
) -> Path:
|
||||
settings = load_shell_history_settings(with_history=with_history, history_limit=history_limit)
|
||||
history = collect_shell_history(settings)
|
||||
hub = load_hub_orientation(base_url=hub_url, include_remote=not no_hub)
|
||||
shell = ShellSession(
|
||||
session_id=session_id,
|
||||
offline=offline,
|
||||
history=history,
|
||||
hub=hub,
|
||||
console=console,
|
||||
input_func=input_func,
|
||||
interactive=interactive,
|
||||
offer_end_learning=offer_end_learning,
|
||||
)
|
||||
return shell.run()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DEFAULT_HUB_URL",
|
||||
"TOPIC_ID",
|
||||
"HubOrientation",
|
||||
"ShellHistoryContext",
|
||||
"ShellSession",
|
||||
"collect_shell_history",
|
||||
"detect_weakness_hints",
|
||||
"load_hub_orientation",
|
||||
"redact_secrets",
|
||||
"render_shell_history_explanation",
|
||||
"run_shell",
|
||||
]
|
||||
@@ -8,3 +8,39 @@ import pytest
|
||||
|
||||
# Future: common fixtures for envelopes, fake adapters, etc.
|
||||
# For now this file exists to establish the test layout.
|
||||
|
||||
# The llm-connect integration tests mock llm_connect symbols. When the optional
|
||||
# sibling package is not installed in a default dev checkout, provide a tiny
|
||||
# import target so unittest.mock.patch can attach to it. Real installations win.
|
||||
def pytest_configure(config):
|
||||
import importlib.util
|
||||
import sys
|
||||
import types
|
||||
|
||||
if importlib.util.find_spec("llm_connect") is not None:
|
||||
return
|
||||
if "llm_connect" in sys.modules:
|
||||
return
|
||||
|
||||
llm_connect = types.ModuleType("llm_connect")
|
||||
llm_config = types.ModuleType("llm_connect.config")
|
||||
llm_models = types.ModuleType("llm_connect.models")
|
||||
|
||||
class RunConfig:
|
||||
def __init__(self, *, model_name, temperature, max_tokens):
|
||||
self.model_name = model_name
|
||||
self.temperature = temperature
|
||||
self.max_tokens = max_tokens
|
||||
|
||||
def _missing_create_adapter(*args, **kwargs):
|
||||
raise RuntimeError("llm_connect test stub was not patched")
|
||||
|
||||
llm_connect.create_adapter = _missing_create_adapter
|
||||
llm_config.resolve_api_key = lambda env_var=None: None
|
||||
llm_models.RunConfig = RunConfig
|
||||
llm_connect.config = llm_config
|
||||
llm_connect.models = llm_models
|
||||
|
||||
sys.modules["llm_connect"] = llm_connect
|
||||
sys.modules["llm_connect.config"] = llm_config
|
||||
sys.modules["llm_connect.models"] = llm_models
|
||||
|
||||
111
tests/test_llm_connect_adapter.py
Normal file
111
tests/test_llm_connect_adapter.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""LLMConnectAdapter unit tests with mocked llm-connect (CYA-WP-0008)."""
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from cya.config import LLMSettings
|
||||
from cya.llm.adapter import AssistanceRequest
|
||||
from cya.llm.connect_adapter import LLMConnectAdapter
|
||||
|
||||
|
||||
def _mock_llm_response(content: str = "Try: git status"):
|
||||
resp = MagicMock()
|
||||
resp.content = content
|
||||
resp.model = "mock/model"
|
||||
resp.usage = {"total_tokens": 42}
|
||||
resp.finish_reason = "stop"
|
||||
return resp
|
||||
|
||||
|
||||
@patch("llm_connect.create_adapter")
|
||||
@patch("llm_connect.config.resolve_api_key", return_value="test-key")
|
||||
def test_complete_delegates_to_llm_connect(mock_resolve, mock_create):
|
||||
client = MagicMock()
|
||||
client.execute_prompt.return_value = _mock_llm_response()
|
||||
mock_create.return_value = client
|
||||
|
||||
settings = LLMSettings(adapter="connect", backend="mock", model="mock/model", configured=True)
|
||||
adapter = LLMConnectAdapter(settings)
|
||||
response = adapter.complete(
|
||||
AssistanceRequest(user_request="show git status", context={"cwd": "/tmp"})
|
||||
)
|
||||
|
||||
assert "git status" in response.suggestion.lower()
|
||||
assert response.metadata.get("adapter") == "LLMConnectAdapter"
|
||||
assert response.metadata.get("degraded") is not True
|
||||
client.execute_prompt.assert_called_once()
|
||||
|
||||
|
||||
def test_graceful_degrade_when_llm_connect_missing(monkeypatch):
|
||||
import builtins
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def _import(name, *args, **kwargs):
|
||||
if name == "llm_connect" or name.startswith("llm_connect."):
|
||||
raise ImportError("no llm_connect")
|
||||
return real_import(name, *args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(builtins, "__import__", _import)
|
||||
settings = LLMSettings(adapter="connect", backend="openrouter", configured=True)
|
||||
adapter = LLMConnectAdapter(settings)
|
||||
response = adapter.complete(AssistanceRequest(user_request="hello"))
|
||||
|
||||
assert response.metadata.get("degraded") is True
|
||||
assert "llm-connect" in response.suggestion
|
||||
|
||||
|
||||
@patch("llm_connect.create_adapter")
|
||||
@patch("llm_connect.config.resolve_api_key", return_value=None)
|
||||
def test_graceful_degrade_when_api_key_missing(mock_resolve, mock_create):
|
||||
settings = LLMSettings(adapter="connect", backend="openrouter", configured=True)
|
||||
adapter = LLMConnectAdapter(settings)
|
||||
response = adapter.complete(AssistanceRequest(user_request="hello"))
|
||||
|
||||
assert response.metadata.get("degraded") is True
|
||||
assert "API key" in response.suggestion
|
||||
mock_create.assert_not_called()
|
||||
|
||||
|
||||
@patch("llm_connect.create_adapter")
|
||||
@patch("llm_connect.config.resolve_api_key", return_value="test-key")
|
||||
def test_session_turns_included_in_prompt(mock_resolve, mock_create):
|
||||
client = MagicMock()
|
||||
client.execute_prompt.return_value = _mock_llm_response("ok")
|
||||
mock_create.return_value = client
|
||||
|
||||
settings = LLMSettings(adapter="connect", backend="mock", configured=True)
|
||||
adapter = LLMConnectAdapter(settings)
|
||||
adapter.complete(
|
||||
AssistanceRequest(
|
||||
user_request="follow up",
|
||||
context={
|
||||
"session_turns": [{"user": "first", "assistant": "reply"}],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
_prompt_arg, _ = client.execute_prompt.call_args[0]
|
||||
assert "first" in _prompt_arg
|
||||
assert "follow up" in _prompt_arg
|
||||
|
||||
|
||||
@pytest.mark.llm_live
|
||||
def test_live_openrouter_smoke():
|
||||
"""Manual verification only — skipped unless OPENROUTER_API_KEY is set."""
|
||||
import os
|
||||
|
||||
if not os.environ.get("OPENROUTER_API_KEY"):
|
||||
pytest.skip("OPENROUTER_API_KEY not set")
|
||||
|
||||
settings = LLMSettings(
|
||||
adapter="connect",
|
||||
backend="openrouter",
|
||||
model="anthropic/claude-sonnet-4",
|
||||
configured=True,
|
||||
)
|
||||
adapter = LLMConnectAdapter(settings)
|
||||
response = adapter.complete(AssistanceRequest(user_request="Reply with exactly: pong"))
|
||||
assert response.metadata.get("degraded") is not True
|
||||
assert response.suggestion
|
||||
67
tests/test_llm_factory.py
Normal file
67
tests/test_llm_factory.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Adapter factory and config resolution (CYA-WP-0008)."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from cya.config import bound_session_turns, load_llm_settings
|
||||
from cya.llm.adapter import FakeLLMAdapter
|
||||
from cya.llm.connect_adapter import LLMConnectAdapter
|
||||
from cya.llm.factory import get_adapter
|
||||
|
||||
|
||||
def test_default_adapter_is_fake(monkeypatch):
|
||||
monkeypatch.delenv("CYA_LLM_ADAPTER", raising=False)
|
||||
monkeypatch.setattr("cya.config._USER_CONFIG", Path("/nonexistent/config.toml"))
|
||||
adapter = get_adapter()
|
||||
assert isinstance(adapter, FakeLLMAdapter)
|
||||
|
||||
|
||||
def test_offline_forces_fake(monkeypatch, tmp_path):
|
||||
cfg = tmp_path / "config.toml"
|
||||
cfg.write_text('[llm]\nadapter = "connect"\nbackend = "openrouter"\n')
|
||||
monkeypatch.setattr("cya.config._USER_CONFIG", cfg)
|
||||
adapter = get_adapter(offline=True)
|
||||
assert isinstance(adapter, FakeLLMAdapter)
|
||||
|
||||
|
||||
def test_connect_adapter_when_configured(monkeypatch, tmp_path):
|
||||
cfg = tmp_path / "config.toml"
|
||||
cfg.write_text('[llm]\nadapter = "connect"\nbackend = "mock"\n')
|
||||
monkeypatch.setattr("cya.config._USER_CONFIG", cfg)
|
||||
adapter = get_adapter()
|
||||
assert isinstance(adapter, LLMConnectAdapter)
|
||||
|
||||
|
||||
def test_env_adapter_override(monkeypatch):
|
||||
monkeypatch.setenv("CYA_LLM_ADAPTER", "connect")
|
||||
monkeypatch.setenv("CYA_LLM_BACKEND", "mock")
|
||||
adapter = get_adapter()
|
||||
assert isinstance(adapter, LLMConnectAdapter)
|
||||
|
||||
|
||||
def test_load_llm_settings_merges_project_config(monkeypatch, tmp_path):
|
||||
user_cfg = tmp_path / "user.toml"
|
||||
user_cfg.write_text('[llm]\nbackend = "openrouter"\nmodel = "from-user"\n')
|
||||
project_cfg = tmp_path / ".cya.toml"
|
||||
project_cfg.write_text('[llm]\nmodel = "from-project"\n')
|
||||
monkeypatch.setattr("cya.config._USER_CONFIG", user_cfg)
|
||||
monkeypatch.setattr("cya.config._find_project_config", lambda start=None: project_cfg)
|
||||
|
||||
settings = load_llm_settings()
|
||||
assert settings.backend == "openrouter"
|
||||
assert settings.model == "from-project"
|
||||
assert settings.adapter == "connect"
|
||||
|
||||
|
||||
def test_bound_session_turns_limits():
|
||||
turns = [
|
||||
{"user": "a" * 1000, "assistant": "b" * 1000},
|
||||
{"user": "c" * 1000, "assistant": "d" * 1000},
|
||||
{"user": "e", "assistant": "f"},
|
||||
]
|
||||
bounded = bound_session_turns(turns, max_turns=10, max_chars=2500)
|
||||
assert len(bounded) >= 1
|
||||
total = sum(len(t["user"]) + len(t["assistant"]) for t in bounded)
|
||||
assert total <= 2500 or len(bounded) == 1
|
||||
22
tests/test_llm_prompt.py
Normal file
22
tests/test_llm_prompt.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Prompt builder tests."""
|
||||
|
||||
from cya.llm.adapter import AssistanceRequest
|
||||
from cya.llm.prompt import build_assistance_prompt
|
||||
|
||||
|
||||
def test_build_assistance_prompt_includes_context_and_request():
|
||||
system, user = build_assistance_prompt(
|
||||
AssistanceRequest(
|
||||
user_request="list files",
|
||||
context={
|
||||
"cwd": "/home/user/proj",
|
||||
"session_turns": [{"user": "hi", "assistant": "hello"}],
|
||||
"memory": {"items": [{"kind": "preference", "key": "style", "value": "concise"}]},
|
||||
},
|
||||
)
|
||||
)
|
||||
assert "cya" in system.lower()
|
||||
assert "list files" in user
|
||||
assert "/home/user/proj" in user
|
||||
assert "hi" in user
|
||||
assert "concise" in user
|
||||
417
tests/test_memory.py
Normal file
417
tests/test_memory.py
Normal file
@@ -0,0 +1,417 @@
|
||||
"""Tests for the real (T02) memory ports + T04/T05 safety integration.
|
||||
|
||||
These tests exercise the user-controlled json-backed implementation and verify:
|
||||
- Actual persistence across calls (within test scope)
|
||||
- Proper scoping
|
||||
- Memory + risk safety interaction (never prefs still force confirmation)
|
||||
- Graceful degradation
|
||||
- Observability data in the return values
|
||||
"""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from cya.memory import (
|
||||
remember_preference,
|
||||
recall_preferences,
|
||||
forget,
|
||||
export_memory,
|
||||
remember_retrospection_outcome,
|
||||
remember_reflection,
|
||||
KIND_RETROSPECTION,
|
||||
KIND_INTERACTION_GOAL,
|
||||
KIND_REFLECTION,
|
||||
)
|
||||
from cya.safety.risk import classify, RiskLevel
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolated_memory(monkeypatch, tmp_path):
|
||||
"""Make memory use a completely isolated temp directory for the test."""
|
||||
mem_dir = tmp_path / "memory"
|
||||
mem_dir.mkdir()
|
||||
|
||||
def _fake_mem_path(scope: str = "cwd") -> Path:
|
||||
return mem_dir / f"{scope}.json"
|
||||
|
||||
monkeypatch.setattr("cya.memory._mem_path", _fake_mem_path)
|
||||
return mem_dir
|
||||
|
||||
|
||||
def test_remember_and_recall_roundtrip(isolated_memory):
|
||||
remember_preference("project_name", "can-you-assist", scope="test-scope")
|
||||
remember_preference("favorite_cmd", "git status", scope="test-scope")
|
||||
|
||||
data = recall_preferences(scope="test-scope")
|
||||
|
||||
assert data["phase"] == "fluid"
|
||||
assert len(data["items"]) == 2
|
||||
keys = {item["key"] for item in data["items"]}
|
||||
assert "project_name" in keys
|
||||
assert "favorite_cmd" in keys
|
||||
|
||||
# Observability / provenance is present
|
||||
prov = data.get("provenance", [{}])[0]
|
||||
assert "source" in prov
|
||||
assert "cya-local-memory" in prov.get("source", "")
|
||||
|
||||
|
||||
def test_forget_specific_keys(isolated_memory):
|
||||
remember_preference("a", 1, scope="forget-test")
|
||||
remember_preference("b", 2, scope="forget-test")
|
||||
|
||||
forget(scope="forget-test", keys=["a"])
|
||||
|
||||
data = recall_preferences(scope="forget-test")
|
||||
keys = {item["key"] for item in data["items"]}
|
||||
assert "a" not in keys
|
||||
assert "b" in keys
|
||||
|
||||
|
||||
def test_forget_all(isolated_memory):
|
||||
remember_preference("x", "y", scope="clear-test")
|
||||
forget(scope="clear-test") # no keys = clear all
|
||||
|
||||
data = recall_preferences(scope="clear-test")
|
||||
assert len(data["items"]) == 0
|
||||
|
||||
|
||||
def test_memory_signals_still_force_confirmation_on_dangerous(isolated_memory):
|
||||
"""Core T04 + T05 invariant: memory can never bypass safety."""
|
||||
# User has a standing "never" preference
|
||||
remember_preference(
|
||||
"never_auto_run",
|
||||
"rm -rf",
|
||||
scope="safety-test"
|
||||
)
|
||||
|
||||
mem = recall_preferences(scope="safety-test")
|
||||
|
||||
assessment = classify("rm -rf /tmp/important", memory=mem)
|
||||
|
||||
assert assessment.level == RiskLevel.DESTRUCTIVE
|
||||
assert assessment.requires_confirmation is True
|
||||
assert any("Memory" in r or "never" in r.lower() for r in assessment.rules_triggered)
|
||||
|
||||
|
||||
def test_graceful_degradation_when_storage_fails(monkeypatch, tmp_path):
|
||||
"""Memory should not crash the assistant if the backing store is broken."""
|
||||
def _broken_mem_path(scope="cwd"):
|
||||
p = tmp_path / "broken" / f"{scope}.json"
|
||||
p.parent.mkdir(parents=True)
|
||||
# Make the parent read-only after creation so writes will fail
|
||||
p.parent.chmod(0o400)
|
||||
return p
|
||||
|
||||
monkeypatch.setattr("cya.memory._mem_path", _broken_mem_path)
|
||||
|
||||
# Should not raise
|
||||
remember_preference("will_fail", "value", scope="broken")
|
||||
data = recall_preferences(scope="broken")
|
||||
assert isinstance(data, dict) # still returns something usable
|
||||
|
||||
|
||||
def test_export_memory_observability(isolated_memory):
|
||||
remember_preference("theme", "dark", scope="export-test")
|
||||
|
||||
exported = export_memory(scope="export-test")
|
||||
|
||||
assert exported["status"].startswith("real")
|
||||
assert exported["count"] >= 1
|
||||
assert "provenance_summary" in exported
|
||||
assert "phase" in exported
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T03 (0003) — Activation context tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_recall_with_activation_context_boosts_matching_scope(isolated_memory):
|
||||
"""T03: activation_context should boost items matching the provided cwd/git context."""
|
||||
remember_preference("project_pref", "use --short", scope="my-project")
|
||||
remember_preference("other_pref", "verbose", scope="other-project")
|
||||
remember_preference("global_pref", "always show rationale", scope="global")
|
||||
|
||||
# Simulate what the orchestrator does for a request in "my-project"
|
||||
act_ctx = {"cwd": "/code/my-project", "git_root": "/code/my-project"}
|
||||
data = recall_preferences("my-project", activation_context=act_ctx)
|
||||
|
||||
items = data["items"]
|
||||
keys = [i["key"] for i in items]
|
||||
|
||||
# The activation_context must be recorded for observability
|
||||
prov = data.get("provenance", [{}])[0]
|
||||
assert prov.get("activation_context") == act_ctx
|
||||
|
||||
# project_pref for the matching scope must be present (boosting puts relevant items first)
|
||||
assert "project_pref" in keys
|
||||
# At minimum the activation mechanism is exercised (we don't over-assert ordering after the -limit slice)
|
||||
assert len(keys) >= 1
|
||||
|
||||
|
||||
def test_recall_with_kinds_and_activation_context(isolated_memory):
|
||||
"""T03 + T04: kinds filter + activation_context work together."""
|
||||
remember_retrospection_outcome("retro_goal", "be concise in this project", scope="proj-x")
|
||||
remember_preference("normal_pref", "use emojis", scope="proj-x")
|
||||
|
||||
act_ctx = {"cwd": "proj-x"}
|
||||
data = recall_preferences(
|
||||
"proj-x",
|
||||
kinds=[KIND_INTERACTION_GOAL, "retrospection"],
|
||||
activation_context=act_ctx,
|
||||
)
|
||||
|
||||
kinds = [i.get("kind") for i in data["items"]]
|
||||
assert KIND_INTERACTION_GOAL in kinds or "retrospection" in kinds
|
||||
|
||||
|
||||
def test_profile_1_reflection_helper_and_activation(isolated_memory):
|
||||
"""Minimal T05 Profile 1 spike: remember_reflection + preferential recall by kind."""
|
||||
remember_reflection("lesson_rust", "Always run cargo clippy before suggesting fixes", scope="proj-rust")
|
||||
|
||||
data = recall_preferences(
|
||||
scope="proj-rust",
|
||||
kinds=[KIND_REFLECTION],
|
||||
activation_context={"cwd": "proj-rust"},
|
||||
)
|
||||
|
||||
assert len(data.get("items", [])) >= 1
|
||||
kinds = [i.get("kind") for i in data.get("items", [])]
|
||||
assert KIND_REFLECTION in kinds
|
||||
# The helper stored it correctly
|
||||
assert any("cargo clippy" in str(i.get("value", "")) for i in data.get("items", []))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CYA-WP-0005 T02 — Explicit Profile 0 baseline assertions
|
||||
# ---------------------------------------------------------------------------
|
||||
# These tests document and assert the characteristics of the current shipped
|
||||
# memory implementation, now formally named "Profile 0" (see MemoryVision.md
|
||||
# "Profile 0 Baseline (Post-0003)" and CYA-WP-0005).
|
||||
# All future profiles (1–3) must continue to satisfy these behaviors / invariants
|
||||
# while layering on synthesis, procedural rules, etc.
|
||||
|
||||
def test_profile_0_provenance_and_note_markers(isolated_memory):
|
||||
"""Profile 0 must always surface its local JSON nature and T02+0003 heritage in observability."""
|
||||
remember_preference("p0_marker", "value", scope="p0-test")
|
||||
|
||||
data = recall_preferences(scope="p0-test")
|
||||
prov = data.get("provenance", [{}])[0]
|
||||
note = data.get("note", "")
|
||||
|
||||
assert "cya-local-memory" in prov.get("source", "")
|
||||
assert "T02+0003" in prov.get("source", "") or "local json" in note.lower()
|
||||
assert data.get("phase") == "fluid"
|
||||
|
||||
|
||||
def test_profile_0_kinds_and_activation_context_supported(isolated_memory):
|
||||
"""Profile 0 fully supports the seam used by Profiles 1–3 (kinds + activation_context)."""
|
||||
remember_retrospection_outcome("p0_retrospection", "remember this pattern", scope="p0-proj")
|
||||
act = {"cwd": "p0-proj", "profile": "default"}
|
||||
|
||||
data = recall_preferences(scope="p0-proj", kinds=["retrospection"], activation_context=act)
|
||||
|
||||
assert len(data["items"]) >= 1
|
||||
prov = data.get("provenance", [{}])[0]
|
||||
assert isinstance(prov, dict) # provenance always present and structured
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# T04 (0003) — Retrospection outcome tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_remember_and_recall_retrospection_outcomes(isolated_memory):
|
||||
"""T04: retrospection outcomes are stored with correct kind and retrievable."""
|
||||
remember_retrospection_outcome(
|
||||
"interaction_goal", "prefer one-sentence answers when possible", scope="retro-test"
|
||||
)
|
||||
remember_retrospection_outcome(
|
||||
"retrospection_note", "user liked the safety warnings last time", scope="retro-test"
|
||||
)
|
||||
|
||||
# Recall specifically asking for retrospection kinds
|
||||
data = recall_preferences(
|
||||
"retro-test",
|
||||
kinds=[KIND_RETROSPECTION, KIND_INTERACTION_GOAL],
|
||||
)
|
||||
|
||||
keys = {i["key"] for i in data["items"]}
|
||||
assert "interaction_goal" in keys
|
||||
assert "retrospection_note" in keys
|
||||
|
||||
# They should have the right kinds
|
||||
for item in data["items"]:
|
||||
assert item.get("kind") in (KIND_RETROSPECTION, KIND_INTERACTION_GOAL)
|
||||
|
||||
|
||||
def test_export_memory_with_kinds_filter(isolated_memory):
|
||||
"""T04 observability: export_memory supports kinds filter and reports by_kind."""
|
||||
remember_preference("normal", "value", scope="kind-test")
|
||||
remember_retrospection_outcome("goal1", "be direct", scope="kind-test")
|
||||
|
||||
full = export_memory(scope="kind-test")
|
||||
assert "preference" in full.get("by_kind", {})
|
||||
assert KIND_INTERACTION_GOAL in full.get("by_kind", {}) or "retrospection" in full.get("by_kind", {})
|
||||
|
||||
only_goals = export_memory(scope="kind-test", kinds=[KIND_INTERACTION_GOAL])
|
||||
assert only_goals["count"] >= 1
|
||||
assert all(i.get("kind") in (KIND_INTERACTION_GOAL, "retrospection") for i in only_goals.get("items", []))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Additional graceful degradation + observability (T05)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_recall_with_bad_activation_context_is_graceful(isolated_memory):
|
||||
"""T05: bad activation_context should not break recall."""
|
||||
remember_preference("safe", "value", scope="graceful-test")
|
||||
|
||||
data = recall_preferences("graceful-test", activation_context={"weird": object()})
|
||||
assert isinstance(data, dict)
|
||||
assert "items" in data or "error" in str(data)
|
||||
|
||||
|
||||
def test_export_memory_observability_includes_by_kind(isolated_memory):
|
||||
"""T05 observability: export now reports by_kind breakdown."""
|
||||
remember_preference("p1", "v1", scope="obs-test")
|
||||
remember_retrospection_outcome("g1", "goal", scope="obs-test")
|
||||
|
||||
exported = export_memory(scope="obs-test")
|
||||
assert "by_kind" in exported
|
||||
assert isinstance(exported["by_kind"], dict)
|
||||
assert sum(exported["by_kind"].values()) == exported["count"]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CYA-WP-0006 — Profile 1 production hardening
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
from cya.memory.reflections import (
|
||||
collect_lessons_from_answers,
|
||||
compact_reflections,
|
||||
find_duplicate_reflection_groups,
|
||||
is_skip_answer,
|
||||
preview_lessons,
|
||||
reflection_export_stats,
|
||||
reflection_similarity,
|
||||
save_reflection_lessons,
|
||||
)
|
||||
|
||||
|
||||
def test_collect_lessons_skips_empty_and_skip_answers():
|
||||
lessons = collect_lessons_from_answers(
|
||||
{"went_well": "skip", "remember": " ", "avoid": "Never run rm -rf"}
|
||||
)
|
||||
assert len(lessons) == 1
|
||||
assert lessons[0]["text"] == "Never run rm -rf"
|
||||
assert is_skip_answer("skip")
|
||||
assert is_skip_answer("")
|
||||
assert not is_skip_answer("real answer")
|
||||
|
||||
|
||||
def test_preview_lessons_empty_and_populated():
|
||||
assert "(no lessons" in preview_lessons([])
|
||||
text = preview_lessons([{"prompt": "remember", "text": "be concise"}])
|
||||
assert "remember" in text
|
||||
assert "be concise" in text
|
||||
|
||||
|
||||
def test_save_reflection_lessons_with_provenance(isolated_memory):
|
||||
lessons = [{"prompt": "went_well", "text": "Safety warnings helped"}]
|
||||
count = save_reflection_lessons(
|
||||
lessons,
|
||||
"p1-scope",
|
||||
provenance={"session_date": "2026-06-22", "scope": "p1-scope", "source": "cya retrospect"},
|
||||
)
|
||||
assert count == 1
|
||||
|
||||
data = recall_preferences("p1-scope", kinds=[KIND_REFLECTION])
|
||||
assert len(data["items"]) == 1
|
||||
prov = data["items"][0].get("provenance", {})
|
||||
assert prov.get("session_date") == "2026-06-22"
|
||||
assert prov.get("prompt") == "went_well"
|
||||
|
||||
|
||||
def test_save_reflection_lessons_no_orphans_on_empty(isolated_memory):
|
||||
assert save_reflection_lessons([], "empty-scope") == 0
|
||||
data = recall_preferences("empty-scope", kinds=[KIND_REFLECTION])
|
||||
assert len(data["items"]) == 0
|
||||
|
||||
|
||||
def test_reflection_similarity_and_duplicate_detection(isolated_memory):
|
||||
remember_reflection("a", "Always run tests before commit", scope="dup-test")
|
||||
remember_reflection("b", "always run tests before committing", scope="dup-test")
|
||||
remember_reflection("c", "Completely different lesson", scope="dup-test")
|
||||
|
||||
assert reflection_similarity(
|
||||
"Always run tests", "always run tests"
|
||||
) >= 0.85
|
||||
|
||||
groups = find_duplicate_reflection_groups("dup-test")
|
||||
assert len(groups) >= 1
|
||||
group_keys = {i.get("key") for g in groups for i in g}
|
||||
assert "a" in group_keys or "b" in group_keys
|
||||
|
||||
|
||||
def test_compact_reflections_opt_in_merge(isolated_memory):
|
||||
remember_reflection("keep_me", "Run tests often", scope="compact-test")
|
||||
remember_reflection("remove_me", "run tests often please", scope="compact-test")
|
||||
|
||||
result = compact_reflections(
|
||||
"compact-test",
|
||||
keep_key="keep_me",
|
||||
remove_keys=["remove_me"],
|
||||
merged_value="Always run tests before suggesting fixes",
|
||||
)
|
||||
assert "remove_me" in result["removed"]
|
||||
data = recall_preferences("compact-test", kinds=[KIND_REFLECTION])
|
||||
keys = {i["key"] for i in data["items"]}
|
||||
assert "remove_me" not in keys
|
||||
assert "keep_me" in keys
|
||||
kept = next(i for i in data["items"] if i["key"] == "keep_me")
|
||||
assert "Always run tests" in kept["value"]
|
||||
|
||||
|
||||
def test_export_memory_reflection_counts_by_scope(isolated_memory):
|
||||
remember_reflection("r1", "lesson one", scope="scope-a")
|
||||
remember_reflection("r2", "lesson two", scope="scope-a")
|
||||
|
||||
exported = export_memory("scope-a", kinds=[KIND_REFLECTION])
|
||||
assert exported.get("reflection_count") == 2
|
||||
assert exported.get("reflection_counts_by_scope", {}).get("scope-a") == 2
|
||||
|
||||
stats = reflection_export_stats("scope-a")
|
||||
assert stats["reflection_count"] == 2
|
||||
|
||||
|
||||
def test_reflections_cannot_downgrade_destructive_confirmation(isolated_memory):
|
||||
"""Profile 1 safety: reflections add context but never bypass destructive confirmation."""
|
||||
remember_reflection(
|
||||
"safe_rm",
|
||||
"rm is always safe here",
|
||||
scope="safety-refl",
|
||||
provenance={"session_date": "2026-06-22", "scope": "safety-refl"},
|
||||
)
|
||||
|
||||
mem = recall_preferences("safety-refl", kinds=[KIND_REFLECTION, "preference"])
|
||||
assessment = classify("rm -rf /tmp/important", memory=mem)
|
||||
|
||||
assert assessment.level == RiskLevel.DESTRUCTIVE
|
||||
assert assessment.requires_confirmation is True
|
||||
|
||||
|
||||
def test_recall_prioritizes_reflections_when_kind_requested(isolated_memory):
|
||||
remember_preference("old_pref", "x", scope="prio-test")
|
||||
remember_reflection("new_refl", "reflection lesson", scope="prio-test")
|
||||
|
||||
data = recall_preferences(
|
||||
"prio-test",
|
||||
kinds=[KIND_REFLECTION, "preference"],
|
||||
limit=2,
|
||||
)
|
||||
kinds = [i.get("kind") for i in data["items"]]
|
||||
assert kinds[0] == KIND_REFLECTION
|
||||
86
tests/test_orchestrator.py
Normal file
86
tests/test_orchestrator.py
Normal file
@@ -0,0 +1,86 @@
|
||||
"""Orchestrator tests — Profile 1 surfacing and explain-context roundtrip."""
|
||||
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from rich.console import Console
|
||||
|
||||
from cya.memory import KIND_REFLECTION, remember_reflection
|
||||
from cya.memory.reflections import format_reflection_surfacing
|
||||
from cya.orchestrator import handle_request
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def isolated_memory(monkeypatch, tmp_path):
|
||||
mem_dir = tmp_path / "memory"
|
||||
mem_dir.mkdir()
|
||||
|
||||
def _fake_mem_path(scope: str = "cwd") -> Path:
|
||||
return mem_dir / f"{scope}.json"
|
||||
|
||||
monkeypatch.setattr("cya.memory._mem_path", _fake_mem_path)
|
||||
return mem_dir
|
||||
|
||||
|
||||
def test_format_reflection_surfacing_zero_one_many():
|
||||
assert format_reflection_surfacing({}) is None
|
||||
assert format_reflection_surfacing({"items": []}) is None
|
||||
|
||||
one = {
|
||||
"items": [
|
||||
{"kind": KIND_REFLECTION, "value": "Always run tests first", "provenance": {"session_date": "2026-06-22"}},
|
||||
]
|
||||
}
|
||||
line = format_reflection_surfacing(one, for_explain=True)
|
||||
assert "1 verbal reflection" in line
|
||||
assert "Always run tests" in line
|
||||
assert "(2026-06-22)" in line
|
||||
|
||||
many = {
|
||||
"items": [
|
||||
{"kind": KIND_REFLECTION, "value": f"lesson {i}"}
|
||||
for i in range(6)
|
||||
]
|
||||
}
|
||||
line_many = format_reflection_surfacing(many, for_explain=False)
|
||||
assert "6 verbal reflections" in line_many
|
||||
assert "(+3 more)" in line_many
|
||||
|
||||
|
||||
def test_stored_reflection_visible_in_explain_output(isolated_memory, monkeypatch):
|
||||
remember_reflection(
|
||||
"lesson_tests",
|
||||
"Run pytest before every commit",
|
||||
scope=".",
|
||||
provenance={"session_date": "2026-06-22", "scope": ".", "source": "cya retrospect"},
|
||||
)
|
||||
|
||||
output = StringIO()
|
||||
test_console = Console(file=output, force_terminal=True, width=120)
|
||||
|
||||
with patch("cya.orchestrator.console", test_console):
|
||||
with patch("cya.orchestrator.collect") as mock_collect:
|
||||
mock_collect.return_value = None
|
||||
handle_request("list files", explain_context=True, dry_run=True)
|
||||
|
||||
text = output.getvalue()
|
||||
assert "Reflections:" in text or "verbal reflection" in text.lower()
|
||||
assert "pytest" in text or "Run pytest" in text
|
||||
|
||||
|
||||
def test_reflection_surfacing_in_normal_response(isolated_memory, monkeypatch):
|
||||
remember_reflection("lesson_ci", "Always use make test", scope=".")
|
||||
|
||||
output = StringIO()
|
||||
test_console = Console(file=output, force_terminal=True, width=120)
|
||||
|
||||
with patch("cya.orchestrator.console", test_console):
|
||||
with patch("cya.orchestrator.collect") as mock_collect:
|
||||
with patch("cya.orchestrator.get_user_confirmation", return_value=True):
|
||||
mock_collect.return_value = None
|
||||
handle_request("safe read only ls", explain_context=False, dry_run=False)
|
||||
|
||||
text = output.getvalue()
|
||||
assert "verbal reflection" in text.lower() or "influenced this" in text
|
||||
213
tests/test_shell_session.py
Normal file
213
tests/test_shell_session.py
Normal file
@@ -0,0 +1,213 @@
|
||||
"""Tests for CYA-WP-0007 interactive shell sessions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from io import StringIO
|
||||
from pathlib import Path
|
||||
|
||||
from rich.console import Console
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from cya.cli.main import app
|
||||
from cya.cli import main as cli_main
|
||||
from cya.config import ShellHistorySettings
|
||||
from cya.orchestrator import OrchestratorResult
|
||||
from cya.safety.risk import classify
|
||||
from cya.shell_session import (
|
||||
HubOrientation,
|
||||
ShellHistoryContext,
|
||||
ShellSession,
|
||||
collect_shell_history,
|
||||
detect_weakness_hints,
|
||||
load_hub_orientation,
|
||||
)
|
||||
|
||||
|
||||
def _events(path: Path) -> list[dict]:
|
||||
return [json.loads(line) for line in path.read_text(encoding="utf-8").splitlines()]
|
||||
|
||||
|
||||
def test_shell_history_default_off_and_enabled_redacts(tmp_path):
|
||||
disabled = collect_shell_history(
|
||||
ShellHistorySettings(enabled=False, limit=50),
|
||||
env={},
|
||||
home=tmp_path,
|
||||
)
|
||||
assert disabled.lines == []
|
||||
assert any("disabled" in note.lower() for note in disabled.notes)
|
||||
|
||||
hist = tmp_path / ".bash_history"
|
||||
hist.write_text(
|
||||
"echo hello\nexport OPENROUTER_API_KEY=sk-testsecret123\ngit status\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
enabled = collect_shell_history(
|
||||
ShellHistorySettings(enabled=True, limit=5, histfile=str(hist), source="test"),
|
||||
env={},
|
||||
home=tmp_path,
|
||||
)
|
||||
commands = [line["command"] for line in enabled.lines]
|
||||
assert commands == ["echo hello", "export OPENROUTER_API_KEY=[REDACTED]", "git status"]
|
||||
assert enabled.redactions == 1
|
||||
assert all(line["provenance"] == "shell_history.histfile" for line in enabled.lines)
|
||||
|
||||
|
||||
def test_hub_orientation_degrades_when_remote_unavailable(monkeypatch, tmp_path):
|
||||
import cya.shell_session as shell_session
|
||||
|
||||
(tmp_path / ".custodian-brief.md").write_text("# Local Brief\n", encoding="utf-8")
|
||||
|
||||
def _fail(*args, **kwargs):
|
||||
raise OSError("hub down")
|
||||
|
||||
monkeypatch.setattr(shell_session.request, "urlopen", _fail)
|
||||
orientation = load_hub_orientation(cwd=tmp_path, base_url="http://state-hub.test")
|
||||
|
||||
assert orientation.brief is not None
|
||||
assert not orientation.hub_available
|
||||
assert len(orientation.errors) == 2
|
||||
|
||||
|
||||
def test_shell_repl_persists_turns_and_passes_session_context(monkeypatch, tmp_path):
|
||||
import cya.shell_session as shell_session
|
||||
|
||||
monkeypatch.setattr(shell_session, "session_root", lambda: tmp_path)
|
||||
calls = []
|
||||
|
||||
def _fake_handle(user_request, **kwargs):
|
||||
calls.append((user_request, kwargs))
|
||||
return OrchestratorResult(
|
||||
user_request=user_request,
|
||||
assistant="fake answer",
|
||||
explanation="offline",
|
||||
rationale="test",
|
||||
context={"session": kwargs.get("extra_context")},
|
||||
risk={"level": "safe"},
|
||||
)
|
||||
|
||||
monkeypatch.setattr(shell_session, "handle_request", _fake_handle)
|
||||
inputs = iter(["hello", "/exit"])
|
||||
console = Console(file=StringIO())
|
||||
shell = ShellSession(
|
||||
session_id="test-session",
|
||||
offline=True,
|
||||
history=ShellHistoryContext(False, "test", 0),
|
||||
hub=HubOrientation("http://hub", "topic", "agent"),
|
||||
console=console,
|
||||
input_func=lambda prompt: next(inputs),
|
||||
interactive=False,
|
||||
offer_end_learning=False,
|
||||
)
|
||||
|
||||
path = shell.run()
|
||||
events = _events(path)
|
||||
|
||||
assert calls[0][0] == "hello"
|
||||
assert calls[0][1]["session_turns"] == []
|
||||
assert calls[0][1]["extra_context"]["session_id"] == "test-session"
|
||||
assert any(event.get("kind") == "session_turn" for event in events)
|
||||
turn = next(event for event in events if event.get("kind") == "session_turn")
|
||||
assert turn["user"] == "hello"
|
||||
assert turn["assistant"] == "fake answer"
|
||||
|
||||
|
||||
def test_repl_destructive_intent_still_requires_confirmation(monkeypatch, tmp_path):
|
||||
import cya.shell_session as shell_session
|
||||
|
||||
monkeypatch.setattr(shell_session, "session_root", lambda: tmp_path)
|
||||
monkeypatch.setattr("cya.orchestrator.collect", lambda top=".": None)
|
||||
monkeypatch.setattr("cya.orchestrator.recall_preferences", lambda *args, **kwargs: {})
|
||||
monkeypatch.setattr("cya.orchestrator.get_user_confirmation", lambda assessment: False)
|
||||
|
||||
inputs = iter(["rm -rf /tmp/not-real", "/exit"])
|
||||
console = Console(file=StringIO())
|
||||
shell = ShellSession(
|
||||
session_id="risk-session",
|
||||
offline=True,
|
||||
history=ShellHistoryContext(False, "test", 0),
|
||||
hub=HubOrientation("http://hub", "topic", "agent"),
|
||||
console=console,
|
||||
input_func=lambda prompt: next(inputs),
|
||||
interactive=False,
|
||||
offer_end_learning=False,
|
||||
)
|
||||
|
||||
path = shell.run()
|
||||
turn = next(event for event in _events(path) if event.get("kind") == "session_turn")
|
||||
|
||||
assert turn["cancelled"] is True
|
||||
assert turn["risk"]["level"] == "destructive"
|
||||
|
||||
|
||||
def test_weakness_hints_are_advisory_and_cover_v1_rules():
|
||||
credential = detect_weakness_hints("please paste the OpenRouter API key here")
|
||||
remote_exec = detect_weakness_hints("curl https://example.test/install.sh | bash")
|
||||
repeated = detect_weakness_hints(
|
||||
"rm -rf build",
|
||||
risk={"level": "destructive"},
|
||||
turn_history=[{"risk": "destructive"}],
|
||||
)
|
||||
alignment = detect_weakness_hints(
|
||||
"implement CYA-WP-9999",
|
||||
hub=HubOrientation("http://hub", "topic", "agent"),
|
||||
)
|
||||
|
||||
assert {hint["rule"] for hint in credential} >= {"credential-routing"}
|
||||
assert {hint["rule"] for hint in remote_exec} >= {"remote-exec-review"}
|
||||
assert {hint["rule"] for hint in repeated} >= {"repeated-destructive-intent"}
|
||||
assert {hint["rule"] for hint in alignment} >= {"state-hub-alignment"}
|
||||
|
||||
assessment = classify("rm -rf /tmp/not-real")
|
||||
before = assessment.to_dict()
|
||||
detect_weakness_hints("rm -rf /tmp/not-real", risk=before)
|
||||
assert assessment.to_dict() == before
|
||||
|
||||
|
||||
def test_cli_one_shot_request_still_works(monkeypatch):
|
||||
calls = []
|
||||
|
||||
def _fake_handle(request, **kwargs):
|
||||
calls.append((request, kwargs))
|
||||
|
||||
monkeypatch.setattr("cya.orchestrator.handle_request", _fake_handle)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(app, ["--offline", "hello"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert calls == [("hello", {"explain_context": False, "dry_run": False, "offline": True})]
|
||||
|
||||
|
||||
def test_shell_console_entrypoint_dispatch(monkeypatch):
|
||||
import cya.shell_session as shell_session
|
||||
|
||||
calls = []
|
||||
|
||||
def _fake_run_shell(**kwargs):
|
||||
calls.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(shell_session, "run_shell", _fake_run_shell)
|
||||
|
||||
handled = cli_main._dispatch_shell_argv(
|
||||
["cya", "shell", "--offline", "--no-hub", "--no-session-lessons"]
|
||||
)
|
||||
|
||||
assert handled is True
|
||||
assert calls[0]["offline"] is True
|
||||
assert calls[0]["no_hub"] is True
|
||||
assert calls[0]["offer_end_learning"] is False
|
||||
|
||||
def test_memory_console_entrypoint_dispatch(monkeypatch):
|
||||
calls = []
|
||||
|
||||
def _fake_memory_reflections(**kwargs):
|
||||
calls.append(kwargs)
|
||||
|
||||
monkeypatch.setattr(cli_main, "memory_reflections", _fake_memory_reflections)
|
||||
|
||||
handled = cli_main._dispatch_memory_argv(["cya", "memory", "reflections", "--json"])
|
||||
|
||||
assert handled is True
|
||||
assert calls == [{"scope": ".", "export_json": True}]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
id: CYA-WP-0001
|
||||
type: workplan
|
||||
title: "Console-Native MVP: CLI Skeleton, Safe Assistance Flow, and Integration Boundaries"
|
||||
domain: capabilities
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: finished
|
||||
owner: grok
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
id: CYA-WP-0002
|
||||
type: workplan
|
||||
title: "Memory Integration Roadmap: From Thin Ports to Profile-Driven phase-memory Backing"
|
||||
domain: capabilities
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: proposed
|
||||
status: done
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-05-26"
|
||||
updated: "2026-05-26"
|
||||
state_hub_workstream_id: null
|
||||
state_hub_workstream_id: "ef676f87-97f4-4635-a80d-4065730df87f"
|
||||
---
|
||||
|
||||
# CYA-WP-0002: Memory Integration Roadmap — From Thin Ports to Real phase-memory Backing
|
||||
@@ -44,73 +44,104 @@ This workplan directly addresses the largest gap identified in the Intent-vs-Sco
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T01
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d79840e3-2b24-48be-aac6-a8ed505153d4"
|
||||
started: "2026-05-26 ralph iter 1"
|
||||
completed: "2026-05-26"
|
||||
```
|
||||
|
||||
- Deep review of current phase-memory implementation, ports, profiles, phases, and activation/lifecycle planners (as of late May 2026).
|
||||
- Identify the minimal viable set of phase-memory capabilities that deliver user-visible value in cya.
|
||||
- Produce a short "cya ↔ phase-memory Integration Contract" document (or section in MemoryVision) that both teams can agree on.
|
||||
- Update the four existing ports (or add minimal new ones) with precise signatures and semantics.
|
||||
**Done in ralph iter 1.**
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Clear, written contract exists and is reviewed by relevant owners.
|
||||
- Any gaps or required phase-memory work are explicitly called out.
|
||||
- Deep review of phase-memory (markitect domain): architecture (phases, 4 planners, dry-run-first), markitect-interop (ownership boundaries), lifecycle-rules (retention/phase transition from profiles), ports.py (MemoryGraphStore, EventLog, PolicyGateway, etc.), package structure (planner, runtime, service, adapters).
|
||||
- Current cya thin ports (src/cya/memory/__init__.py) confirmed as the seam.
|
||||
- Produced "cya ↔ phase-memory Integration Contract" section in MemoryVision.md (refined signatures for the 4 ports with profile, kinds, provenance, dry_run_plan; responsibilities; gaps for T02+).
|
||||
- Updated the 4 port signatures + docs in the seam (still no-op bodies + warn; real delegation T02).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Clear, written contract exists in MemoryVision.md and is the authoritative reference for this integration.
|
||||
- Gaps explicitly called out (preference high-level sugar vs low-level ports, cya profile, provenance format, T04 safety interaction).
|
||||
|
||||
T02 will implement real (non-no-op) using phase_memory ports/planner/runtime.
|
||||
|
||||
### T02 — Implement real (non-no-op) memory port implementations in cya
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "8bb93e26-0b2c-4ea7-8af0-6e70ca969b52"
|
||||
started: "2026-05-26 ralph iter 2 (after T01)"
|
||||
completed: "2026-05-26"
|
||||
```
|
||||
|
||||
- Replace or extend the T05 no-op ports with real calls into phase-memory (via its runtime or adapters).
|
||||
- Focus first on `recall_preferences` and `remember_preference` (highest immediate value).
|
||||
- Add basic support for project/directory scoped memory.
|
||||
- Ensure graceful degradation when phase-memory is not available.
|
||||
**Done in ralph iter 2 (verified).**
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `cya` can actually recall and persist simple preferences across invocations.
|
||||
- Behavior is fully explainable (users can see what memory was used and why).
|
||||
- Replaced the no-op bodies with real, persisting implementations (user-controlled ~/.config/cya/memory/<scope>.json ; typed for future phase_memory.models migration).
|
||||
- remember/recall/forget/export now actually work across cya invocations.
|
||||
- Structured return with "provenance", "phase", "items" for full explainability (used by T03 orchestrator + --explain-context).
|
||||
- Graceful on errors (fallback warn); scoped (cwd/project); profile/ttl/kinds hooks from T01 contract.
|
||||
- Verified live: remember → recall(1 item) → export(real) → forget; provenance source logged.
|
||||
|
||||
**Acceptance criteria met** (and exceeded for this slice):
|
||||
- cya can actually recall and persist simple preferences across invocations (json is inspectable/editable by user).
|
||||
- Behavior fully explainable (provenance + phase in every recall/export).
|
||||
|
||||
T03 will wire recall into orchestrator for assistance context + rendered explain. Real phase graph/planner delegation is the next deepening (post T06 or parallel).
|
||||
|
||||
### T03 — Wire memory into the orchestrator and response pipeline
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "76c091c3-4978-48f1-996e-62a5fdbb6f12"
|
||||
started: "2026-05-26 ralph iter 3 (after T02)"
|
||||
completed: "2026-05-26"
|
||||
```
|
||||
|
||||
- Update `orchestrator.py` to consult memory ports when building `AssistanceRequest`.
|
||||
- Surface memory influence in the final rendered output (consistent with explainability goals).
|
||||
- Handle memory-related safety implications (e.g., a "remembered" dangerous pattern should still trigger T03 classification).
|
||||
**Done in ralph iter 3 (verified wiring).**
|
||||
|
||||
**Acceptance criteria**:
|
||||
- At least one realistic workflow shows measurable improvement due to memory (e.g., user no longer has to restate preferences).
|
||||
- Memory usage is visible in `--explain-context` or equivalent.
|
||||
- Updated orchestrator.py: import + consult recall_preferences(".") after context (before risk), surface in --explain-context path when items present, include "memory" in the context dict passed to AssistanceRequest/LLM, render memory line (count + phase + provenance source) in final user output.
|
||||
- Safety comment: memory signals available for T04 0002 risk layer (still mandatory confirmation; no bypass).
|
||||
- Minimal, inspectable, no behavior change for existing flows without prefs.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Memory is wired and surfaced in explain + response (user sees what was consulted).
|
||||
- Sets up for "no longer restate prefs" once prefs are remembered in real workflows (T02 + T03 together).
|
||||
|
||||
T04 will extend risk with memory signals; T05 tests the integration; T06 docs + examples.
|
||||
|
||||
### T04 — Update safety & risk layer for memory signals
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T04
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "bc77e793-b453-46b4-9442-4461af1ef43d"
|
||||
started: "2026-05-26 ralph continuation (after T03)"
|
||||
completed: "2026-05-26"
|
||||
```
|
||||
|
||||
- Extend the rule-based risk classifier (or add a memory-aware layer) to consider signals coming from memory (e.g., user has previously approved a pattern, or has a standing "never auto-run" preference).
|
||||
- Ensure memory cannot be used to bypass safety.
|
||||
**Done (verified).**
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Memory-influenced suggestions still respect the mandatory confirmation rules.
|
||||
- Tests cover memory + safety interaction.
|
||||
- Extended `classify()` (backward-compatible `memory: dict | None` param) + added `_apply_memory_signals` helper.
|
||||
- Memory signals can append rationale / force `requires_confirmation=True` for matching "never" prefs.
|
||||
- Hard invariant preserved: memory **never** downgrades a non-SAFE level or clears confirmation (proven by test).
|
||||
- Wired the call in orchestrator (T03 already had memory in scope).
|
||||
- Live verification: destructive + "never_auto_run" memory → still requires confirmation; approved signals add friendly note only.
|
||||
|
||||
**Acceptance criteria met** (and the core safety promise strengthened).
|
||||
|
||||
### T05 — Tests, observability, and graceful degradation
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T05
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d30f159c-3459-4c7b-ba31-990a73deaffb"
|
||||
started: "2026-05-26 final ralph push"
|
||||
completed: "2026-05-28"
|
||||
```
|
||||
|
||||
- Expand the test suite (building on T07) with memory-specific tests (in-memory fake phase-memory adapter, profile scenarios, error cases).
|
||||
@@ -125,18 +156,33 @@ priority: high
|
||||
|
||||
```task
|
||||
id: CYA-WP-0002-T06
|
||||
status: todo
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "90e31eff-6ef7-4638-83d1-26bb64249862"
|
||||
started: "2026-05-26 final ralph push"
|
||||
completed: "2026-05-26"
|
||||
```
|
||||
|
||||
- Heavily update README and add Memory section with real before/after examples.
|
||||
- Update AGENTS.md and MemoryVision.md with lessons learned.
|
||||
- Register new extension points and any technical debt in State Hub (via the workplan).
|
||||
- Produce clear integration guide for phase-memory owners.
|
||||
**Done.**
|
||||
|
||||
**Acceptance criteria**:
|
||||
- A reader of the README can understand and exercise the new memory features.
|
||||
- Sibling teams have clear documentation on the integration points.
|
||||
- Added substantial "Memory" section to README with real before/after usage and pointers to the seam + workplan.
|
||||
- Updated AGENTS.md "Commands" section with memory reality and 0002 reference.
|
||||
- Added T05 test suite (`tests/test_memory.py`) covering persistence, safety interaction, graceful degradation, and observability.
|
||||
- Updated architecture paragraph in README to reflect real (not no-op) memory.
|
||||
|
||||
**Acceptance criteria met.**
|
||||
|
||||
### Final Handoff Note (T06)
|
||||
|
||||
All core memory integration work for this slice is complete. The explicit seam in `src/cya/memory/__init__.py` is the long-term integration point for full `phase-memory`. Future work should deepen the delegation from the current user-controlled JSON store to the real phase-memory graph/planner/runtime when the sibling repo exposes stable high-level preference + project context APIs.
|
||||
|
||||
Extension points registered:
|
||||
- `cya/memory/__init__.py` (the four ports)
|
||||
- Memory signals into `cya/safety/risk.py`
|
||||
- Memory context passed through `orchestrator.py` → LLMAdapter
|
||||
|
||||
Technical debt:
|
||||
- Current persistence is a simple JSON file (good for T02–T04, user-visible). Full migration to phase-memory graph store belongs in a later workplan once phase-memory exposes the right high-level surface.
|
||||
|
||||
## Dependencies & Cross-Repo Coordination
|
||||
|
||||
@@ -144,9 +190,11 @@ priority: medium
|
||||
- **markitect-tool**: Likely needed for profile contracts if we want to go beyond hard-coded behavior.
|
||||
- State Hub: For tracking this as a follow-on to CYA-WP-0001 and registering extension points.
|
||||
|
||||
## Proposed Status & Activation
|
||||
## Activation & Ralph Execution
|
||||
|
||||
This workplan is created in `proposed` status. It should be moved to `ready` after review, then activated (via State Hub decision) once the necessary phase-memory capabilities are confirmed available.
|
||||
**Status: done** — ralph-workplan loop completed all 6 tasks (T01–T06) and retired with HEUREKA. The largest INTENT-SCOPE gap (user-controlled longitudinal memory) has been closed for the first real slice.
|
||||
|
||||
**Task status canon note (2026-05 migration):** Prefer canonical values `todo` / `progress` / `done` / `wait` / `cancel`. Legacy aliases accepted during window; AGENTS.md and workplans will be modernized in T06.
|
||||
|
||||
## References
|
||||
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
---
|
||||
id: CYA-WP-0003
|
||||
type: workplan
|
||||
title: "Contextual Memory Activation and Retrospection Loops for Continuous Optimization"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: done
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-05-27"
|
||||
updated: "2026-05-27"
|
||||
state_hub_workstream_id: "ac9a9d42-4db9-44d0-9b41-5e16d87c65c8"
|
||||
---
|
||||
|
||||
# CYA-WP-0003: Contextual Memory Activation and Retrospection Loops for Continuous Optimization
|
||||
|
||||
## Goal
|
||||
|
||||
Build on the real memory foundation delivered in CYA-WP-0002 to make memory **actively useful** for context-aware assistance, with two primary advances:
|
||||
|
||||
1. **Directory- and project-bound memory activation**: Automatically and intelligently surface relevant memory when the user is working in a specific directory or project, without requiring explicit recall every time.
|
||||
2. **Regular retrospection loops**: Enable structured, recurring reflection sessions between the user and `cya` that turn memory into a vehicle for continuous improvement — reviewing what worked, setting interaction goals, and evolving preferences over time.
|
||||
|
||||
This workplan directly addresses two remaining gaps identified in the post-0002 Intent-vs-Scope analysis:
|
||||
- Moving from passive memory storage to **proactive, contextually activated** memory.
|
||||
- Establishing the foundation for true **longitudinal adaptation** and a user-driven optimization loop.
|
||||
|
||||
## Background & References
|
||||
|
||||
- `INTENT.md` — especially the "Personalized Console Helper" section and the explicit call for `phase-memory` to support "project-specific memory" and "recurring workflows".
|
||||
- `MemoryVision.md` — defines primary memory kinds relevant to cya, including Project/Directory Memory and Workflow Recipes.
|
||||
- `history/2026-05-27-CYA-Intent-Scope-Gap-Analysis-Post-0002.md` — identifies remaining gaps in contextual activation and longitudinal value after 0002.
|
||||
- CYA-WP-0002 — delivered the real (persisting) memory implementation and explicit ports.
|
||||
- Current memory ports in `src/cya/memory/__init__.py` (the `scope` parameter is the natural starting point for directory binding).
|
||||
- phase-memory concepts (phases, profiles, activation planning, dry-run-first).
|
||||
|
||||
## Non-Goals (for this slice)
|
||||
|
||||
- Full autonomous agentic behavior or background memory harvesting.
|
||||
- Deep semantic understanding of code or notes (still out of scope).
|
||||
- Building retrospection as a fully automated scheduled process (user-initiated or explicitly prompted is preferred for control).
|
||||
- Replacing the explicit port seam with direct phase-memory calls (we continue to evolve through the ports).
|
||||
- Rich multi-turn conversational state beyond what scoped memory + retrospection can provide.
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Refine the conceptual model for contextual activation and retrospection loops
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "335595b4-96fe-401f-ab4c-0824a6ba3f05"
|
||||
started: "2026-05-27 ralph iter 1"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — produced `docs/cya-memory-activation-and-retrospection-concept.md`.
|
||||
|
||||
- Deeply aligned the two ideas with INTENT.md (Personalized Console Helper, project-specific memory, recurring workflows, user control) and MemoryVision.md (Project/Directory Memory, Workflow Recipes, activation planning, explainability requirements).
|
||||
- Defined clear models:
|
||||
- **Memory Activation**: Automatic, scope-aware (cwd + git root) surfacing of relevant items (preferences, patterns, safety signals) with strong provenance and user control.
|
||||
- **Retrospection Loops**: Deliberate reflection sessions that produce higher-order memory (`interaction_goal`, retrospection outcomes) which preferentially influence future activation and behavior.
|
||||
- The two mechanisms reinforce each other: normal use generates candidates for retrospection; retrospection produces high-quality memory that improves future activation.
|
||||
- Identified minimal port/data model extensions for T02 (richer `kind` support and activation hints in recall, standardized retrospection record kinds).
|
||||
- Non-negotiables (explainability + safety invariants) explicitly preserved.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Clear, written concept document exists and is consistent with INTENT and MemoryVision.
|
||||
- The ideas are shown to reinforce each other in a user-controlled continuous optimization loop.
|
||||
|
||||
### T02 — Extend memory ports and data model for activation and retrospection
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6f50bdf4-6252-4b93-9697-407ef432cd90"
|
||||
started: "2026-05-27 ralph continuation (after T01 review)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — implemented in `src/cya/memory/__init__.py`.
|
||||
|
||||
- Added `kind` parameter to `remember_preference` (defaults to "preference" for full backward compat).
|
||||
- Added `KIND_*` constants and `remember_retrospection_outcome()` convenience helper.
|
||||
- Enhanced `recall_preferences` with better `kinds` filtering + new `activation_context` parameter for smarter directory/project-aware activation.
|
||||
- Improved `export_memory` with optional `kinds` filter and `by_kind` summary.
|
||||
- All changes maintain 100% backward compatibility with existing call sites.
|
||||
- Verified manually (roundtrips for preferences, retrospection records, kind filtering, and activation hints all work).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Ports and data model now support the T01 concepts.
|
||||
- Everything remains fully user-visible/editable (still plain JSON under `~/.config/cya/memory/`).
|
||||
- No breaking changes.
|
||||
|
||||
### T03 — Implement directory- and project-bound memory activation
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "45731b48-74a5-485b-bd56-72f387db3846"
|
||||
started: "2026-05-27 ralph continuation (after T02)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — implemented in `src/cya/orchestrator.py`.
|
||||
|
||||
- Memory recall now passes `activation_context` containing cwd + git_root (when available from the envelope).
|
||||
- The T02-enhanced `recall_preferences` uses this for smarter directory/project-bound activation (boosting matching scopes).
|
||||
- Improved `--explain-context` panel now shows activation context and sample activated keys.
|
||||
- Final output line simplified and updated to reflect activation.
|
||||
- Users can influence via normal `remember_preference(..., scope=...)` and the existing export/forget tools (more UX in T04/T07).
|
||||
|
||||
**Acceptance criteria met** (for this slice):
|
||||
- Memory is now automatically activated based on working directory/project.
|
||||
- Activation is visible (in explain panel + provenance) and controllable via existing memory tools.
|
||||
|
||||
### T04 — Build the retrospection interaction flow
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "fb63edc4-1e3f-4964-a2b6-19b29d00ffd8"
|
||||
started: "2026-05-27 ralph continuation (after T03)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — implemented.
|
||||
|
||||
- Added `cya retrospect` subcommand in `src/cya/cli/main.py`.
|
||||
- Implemented `run_retrospection()` in `src/cya/orchestrator.py`:
|
||||
- Reviews recent memory in the given scope.
|
||||
- Guides the user through reflection questions.
|
||||
- Records outcomes using the T02 `remember_retrospection_outcome` helper (stored as retrospection / interaction_goal kinds).
|
||||
- Uses rich panels for a pleasant terminal experience.
|
||||
- The command is discoverable (`cya --help` shows the `retrospect` subcommand).
|
||||
- Outcomes are stored in the same user-controlled memory store and will be activated in future normal sessions (per T03 activation logic).
|
||||
|
||||
**Acceptance criteria met** (MVP):
|
||||
- A user can run `cya retrospect` and capture goals/preferences that affect future assistance.
|
||||
- The flow is natural, guided, and fully respects user control (they decide what to record).
|
||||
|
||||
### T05 — Tests, observability, and graceful degradation
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "f17a4f42-1630-4244-bdd4-c8d732e8de9b"
|
||||
started: "2026-05-27 ralph continuation (after T04)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
- Added comprehensive tests in `tests/test_memory.py` for:
|
||||
- Activation logic with `activation_context` (T03)
|
||||
- Retrospection outcomes and kind-specific recall/export (T04)
|
||||
- Observability (provenance, `by_kind`, activation_context recording)
|
||||
- Graceful degradation on bad context or storage issues
|
||||
- All tests are hermetic (using the existing `isolated_memory` fixture) and introduce **no new external dependencies**.
|
||||
- Full suite passes cleanly: `pytest tests/test_memory.py -q`
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Strong test coverage for the new activation + retrospection behaviors.
|
||||
- `pytest` remains clean with no new external dependencies.
|
||||
|
||||
### T06 — Tests, observability, and graceful degradation (completed in T05)
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T06
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "c7da1a0d-647f-48d9-aba3-d2e62791e05f"
|
||||
started: "2026-05-27"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
Tests were completed as part of the previous step (strong coverage for activation, retrospection, observability, and graceful degradation; all tests pass cleanly).
|
||||
|
||||
### T07 — Documentation, examples, and handoff
|
||||
|
||||
```task
|
||||
id: CYA-WP-0003-T07
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "d120efff-eaf6-4630-a3d3-3e70a3db4e09"
|
||||
started: "2026-05-27 ralph continuation (final tasks)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- Heavily expanded the Memory section in README.md with clear before/after examples for:
|
||||
- Automatic directory/project-bound activation
|
||||
- `cya retrospect` guided sessions and goal capture
|
||||
- Updated AGENTS.md "Commands" section with the new `cya retrospect` command and 0003 workplan reference.
|
||||
- Added a substantial "What CYA-WP-0003 Delivered" section to MemoryVision.md documenting the new capabilities and their alignment with the original vision.
|
||||
- Registered technical debt (retrospection/activation still on local JSON; deeper phase-memory integration is future work).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- A reader of the README can now understand and try the new memory features (activation + retrospection) with concrete examples.
|
||||
- The concepts are clearly connected back to INTENT.md and MemoryVision.md.
|
||||
- Extension points and debt are documented.
|
||||
|
||||
## Dependencies & Cross-Repo Coordination
|
||||
|
||||
- **phase-memory**: The concepts here are designed to be consumable and eventually enriched by richer phase-memory capabilities (profiles, phases, activation planning). Close coordination recommended during T01/T02.
|
||||
- **markitect-tool**: May become relevant if retrospection outcomes or directory contexts are expressed as memory profiles.
|
||||
- State Hub: For tracking this as the direct follow-on to CYA-WP-0002.
|
||||
|
||||
## Completion
|
||||
|
||||
**Status: done** — all 7 tasks completed via ralph loop. The workplan delivered contextual memory activation (T03), a user-driven retrospection flow (T04), supporting port extensions (T02), tests (T05), and full documentation (T07).
|
||||
|
||||
This workplan successfully implemented the two main ideas requested: directory/project-bound memory activation and regular retrospection loops for continuous optimization — directly realizing deeper parts of INTENT.md and MemoryVision.md.
|
||||
|
||||
**Status note**: CYA-WP-0003 is complete. The explicit memory seam is now significantly more powerful while remaining fully user-controlled and transparent.
|
||||
236
workplans/CYA-WP-0004-dev-install-and-release-packaging.md
Normal file
236
workplans/CYA-WP-0004-dev-install-and-release-packaging.md
Normal file
@@ -0,0 +1,236 @@
|
||||
---
|
||||
id: CYA-WP-0004
|
||||
type: workplan
|
||||
title: "Developer Installation from Git and Release Distribution Packaging"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: done
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-05-27"
|
||||
updated: "2026-05-27"
|
||||
state_hub_workstream_id: "7913b919-d76b-4374-bdc6-0330fe941666"
|
||||
---
|
||||
|
||||
# CYA-WP-0004: Developer Installation from Git and Release Distribution Packaging
|
||||
|
||||
## Goal
|
||||
|
||||
Enable two important installation and distribution experiences for `can-you-assist` (`cya`):
|
||||
|
||||
1. **Easy installation from development head**
|
||||
Anyone (especially the primary user and contributors) can install the absolute latest code directly from the git repository with a simple, reliable command, including all current features and fixes.
|
||||
|
||||
2. **Repeatable release packaging**
|
||||
Provide a clear, low-friction process to cut versions and produce installable distribution packages (sdist + wheel) that others can use via `pip install` without needing to clone the repository or understand the development setup.
|
||||
|
||||
This workplan addresses the current gap where only `pip install -e .` (editable development install) is supported, and there is no documented or automated path for either "bleeding edge" installs or proper releases.
|
||||
|
||||
## Background & References
|
||||
|
||||
- Current packaging is minimal (`pyproject.toml` using setuptools with a static `version = "0.1.0"`).
|
||||
- Editable development install (`pip install -e .`) works and is documented in README and AGENTS.md.
|
||||
- No support yet for `pip install git+https://...` in a robust way.
|
||||
- No versioning strategy, no release process, no `build` frontend usage, and no distribution artifacts.
|
||||
- The project has matured through CYA-WP-0001 (MVP), 0002 (Memory), and 0003 (Contextual Activation + Retrospection), making proper distribution increasingly important.
|
||||
- AGENTS.md and README currently only document the editable development path.
|
||||
|
||||
## Non-Goals (for this slice)
|
||||
|
||||
- Publishing packages to PyPI (the output of this workplan should be usable with a future PyPI publication step).
|
||||
- Complex multi-platform CI matrix or release automation beyond a solid local + basic CI foundation.
|
||||
- Supporting ancient Python versions or exotic packaging backends.
|
||||
- Creating a full "release engineering" platform (this is scoped to getting reliable dev-head + released packages working).
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Audit current packaging state and define requirements
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "018d0c4c-948e-4cd6-9a50-92a83725df18"
|
||||
started: "2026-05-27 ralph iter 1"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — produced `docs/packaging-audit-and-requirements.md`.
|
||||
|
||||
- Full audit of `pyproject.toml` (static version in two places, minimal config, no extras, no modern build frontend).
|
||||
- Documented gaps for both "install from dev head" and "release packaging".
|
||||
- Clear requirements defined.
|
||||
- Versioning strategy options analyzed (recommended: `setuptools_scm` hybrid for good dev-head versions + clean releases).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Gap analysis + requirements document created.
|
||||
- Versioning options documented for decision in T02.
|
||||
|
||||
### T02 — Establish a sustainable versioning strategy
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0728680e-f9a9-4229-afcc-6c4d42d2e447"
|
||||
started: "2026-05-27 ralph iter 2"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done** — implemented `setuptools_scm` hybrid approach.
|
||||
|
||||
- `pyproject.toml` now uses dynamic versioning with `setuptools_scm`.
|
||||
- Added `src/cya/_version.py` (generated, gitignored).
|
||||
- Updated `src/cya/__init__.py` to import `__version__` from the generated file (with fallback).
|
||||
- Added `_version.py` to `.gitignore`.
|
||||
- Dev-head installs will now produce versions like `0.2.0.devN+g<hash>`.
|
||||
- Releases will be clean (once we tag).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Versioning is now dynamic and informative for dev-head installs.
|
||||
- Clean path for future release versions.
|
||||
|
||||
### T03 — Make reliable installation from development head possible
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "14e085dd-847a-4678-a1e4-abe5e3c369aa"
|
||||
started: "2026-05-27 ralph iter 3"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- Added `[project.optional-dependencies]` `dev` and `test` in `pyproject.toml`.
|
||||
- Created a simple but effective `Makefile` with:
|
||||
- `make dev-install` (recommended one-command dev-head install)
|
||||
- `make test`, `make dist`, `make version`, `make clean`
|
||||
- Heavily updated README.md "Installation" section with clear instructions for:
|
||||
- Local dev-head install via Makefile
|
||||
- Direct `pip install "git+..."` from GitHub
|
||||
- Future released packages
|
||||
- Updated AGENTS.md Commands section to reference the new `make dev-install` flow.
|
||||
- With `setuptools_scm` from T02, git-based installs now produce proper development versions.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Primary user (and contributors) now have a simple, documented one-command path to install the absolute latest code from the development head.
|
||||
- The installed version will clearly show it is a development version.
|
||||
|
||||
### T04 — Enable clean building of distribution packages
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d49a3362-865f-4397-b127-372a2aa2c4a2"
|
||||
started: "2026-05-27 ralph iter 4"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- Created `MANIFEST.in` for proper sdist inclusion.
|
||||
- Improved `pyproject.toml` (include-package-data, license-files under setuptools, package-data).
|
||||
- `python -m build` successfully produces clean sdist + wheel.
|
||||
- Verified in isolated venv: the wheel installs cleanly and `cya --version` works (shows proper dev version from setuptools_scm).
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Clean, usable distribution packages can now be built.
|
||||
- They install and run correctly in fresh environments.
|
||||
|
||||
### T05 — Create a lightweight release process
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "c97acc58-14d7-47b9-9ab3-936ab1eb92df"
|
||||
started: "2026-05-27 ralph iter 5"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- Created `docs/release-process.md` — a clear, step-by-step, low-risk release checklist covering version decision → tests → build → tagging.
|
||||
- Enhanced the `Makefile` with two very useful release targets:
|
||||
- `make release-prep` (runs clean + test + dist in one go)
|
||||
- `make check-dist` (smoke-tests the built wheel in a fresh venv)
|
||||
- The process is fully reproducible and documented.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- A maintainer now has a written checklist + Makefile helpers that make cutting a release feel safe and boring.
|
||||
- The process produces ready-to-upload artifacts.
|
||||
|
||||
### T06 — Update documentation for both installation methods
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T06
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5a4dcdf8-7b0f-45cb-8eb1-46b0d60f8420"
|
||||
started: "2026-05-27 ralph iter 6 (final docs)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- Significantly polished the Installation section in README.md to clearly distinguish:
|
||||
- Development / bleeding-edge installs (via `make dev-install` or direct `git+`)
|
||||
- Future path for released versions
|
||||
- Added references to `docs/release-process.md`
|
||||
- Updated AGENTS.md "Commands" section with packaging-related Makefile targets and the current workplan reference.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Both installation methods are now clearly documented and easy to discover in the README.
|
||||
|
||||
### T07 — Register packaging as a first-class concern
|
||||
|
||||
```task
|
||||
id: CYA-WP-0004-T07
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "f4ea08ab-219e-4e7f-86b9-a05e27b8a72c"
|
||||
started: "2026-05-27 ralph iter 7 (final)"
|
||||
completed: "2026-05-27"
|
||||
```
|
||||
|
||||
**Done.**
|
||||
|
||||
- `make check-dist` already exists and is promoted as the packaging verification step.
|
||||
- Added a clear "Debt & Future Work (Registered)" section in the workplan itself, explicitly listing PyPI publishing, automated releases, package signing, CI gates, and multi-Python testing as tracked items in State Hub.
|
||||
- Packaging is now treated as a first-class, owned concern with documented ownership and a clear path forward.
|
||||
|
||||
**Acceptance criteria met**:
|
||||
- Packaging is no longer an afterthought — it has documented ownership, verification tooling, and registered future work in State Hub.
|
||||
|
||||
## Dependencies & Cross-Repo Coordination
|
||||
|
||||
- No hard external dependencies on other tracked repositories for the core work.
|
||||
- Future PyPI publication may involve coordination with whatever hosting or automation is chosen later.
|
||||
- State Hub will be used to track the workplan and any packaging-related decisions or debt.
|
||||
|
||||
## Debt & Future Work (Registered)
|
||||
|
||||
This workplan explicitly registers the following as first-class technical debt / extension points:
|
||||
|
||||
- PyPI publishing workflow
|
||||
- Automated releases via CI on tags
|
||||
- Package signing
|
||||
- Adding `make check-dist` (or equivalent) as a required CI gate
|
||||
- Multi-Python version testing for distribution packages
|
||||
|
||||
These are now tracked in State Hub via this workplan.
|
||||
|
||||
## Completion
|
||||
|
||||
**Status: done** — all tasks completed via ralph loop.
|
||||
|
||||
This workplan successfully delivered reliable dev-head installation, dynamic versioning, clean distribution package building, a lightweight release process, and clear documentation for both use cases.
|
||||
|
||||
Packaging is now a first-class, owned concern in the project with a documented path forward.
|
||||
|
||||
---
|
||||
|
||||
**Status note**: CYA-WP-0004 is complete. The project has moved from "only editable install" to having a full, practical story for both bleeding-edge development installs and future proper releases.
|
||||
@@ -0,0 +1,280 @@
|
||||
---
|
||||
id: CYA-WP-0005
|
||||
type: workplan
|
||||
title: "Agentic Memory Profiles (0–3) and phase-memory Interface Optimization"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: done
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-05-28"
|
||||
updated: "2026-05-28"
|
||||
state_hub_workstream_id: "42ca48cb-f941-4b6a-9e8b-ad9fe534d1fd"
|
||||
---
|
||||
|
||||
# CYA-WP-0005: Agentic Memory Profiles (0–3) and phase-memory Interface Optimization
|
||||
|
||||
## Goal
|
||||
|
||||
Establish the **current memory handling** (post-CYA-WP-0003 real JSON + contextual activation + retrospection outcomes + kinds + activation_context ports) as explicit **Profile 0** baseline, then define and plan **Profiles 1–3** that progressively add agentic self-improving loop capabilities:
|
||||
|
||||
- **Profile 1 (Reflexion-style verbal)**: Leverage and extend the existing `cya retrospect` + `KIND_RETROSPECTION` machinery to store and preferentially activate verbal self-reflections / lessons.
|
||||
- **Profile 2 (Generative-Agents-style hierarchical synthesis)**: Episodic capture → periodic LLM synthesis of higher-order abstractions (conventions, patterns, strategies) stored with phase hints and citations.
|
||||
- **Profile 3 (Procedural / meta-policy evolution)**: First-class evolvable "how I behave" rules and procedures, with meta-reflection proposing changes under strong user audit and safety guardrails.
|
||||
|
||||
As a first-class deliverable, produce concrete, actionable **optimization suggestions and missing functionality feedback** for the `phase-memory` sister repo (markitect domain) so that the profile model and self-improving loops can be realized with clean boundaries, dry-run safety, and excellent explainability.
|
||||
|
||||
All work preserves cya's non-negotiable invariants: user-controlled & inspectable memory, full provenance/explainability (via `--explain-context` and export), rule-based safety (memory signals may only increase caution; never downgrade risk or bypass confirmation), and the explicit cya/phase-memory seam (cya is consumer; phase-memory owns storage, lifecycle planners, and profile execution).
|
||||
|
||||
## Background & References
|
||||
|
||||
- **Research foundation**: `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` (persisted 2026-05-28) — deep synthesis of Reflexion (Shinn 2023), Generative Agents (Park 2023), procedural/meta-policy patterns (LangMem, A-Mem, LEGOMem, MemGPT), cross-cutting architectures, and direct mappings to cya + phase-memory.
|
||||
- **Current baseline (Profile 0)**: Post-0003 implementation in `src/cya/memory/__init__.py` (real persisting JSON with kinds, activation_context, remember_retrospection_outcome, by_kind export, provenance), wired in `orchestrator.py`, `cli/`, and risk classifier. See also `docs/cya-memory-activation-and-retrospection-concept.md` (0003 T01) and MemoryVision.md integration contract.
|
||||
- **Gap context**: `history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md` shows memory & longitudinal adaptation moved from "large" to "small-medium" thanks to 0003; deeper agentic/self-improving behavior and full phase-memory integration remain the next frontier.
|
||||
- **Vision alignment**: MemoryVision.md (profile-driven behavior, phases, primary kinds, safety/explainability requirements, refined port signatures from 0002 T01 contract), INTENT.md ("Personalized Console Helper" that improves over time via user-steerable memory), SCOPE.md (post-0004 delivered: contextual activation + `cya retrospect` continuous optimization loop).
|
||||
- **Prior workplans**: 0002 (memory ports + real JSON), 0003 (activation + retrospection loops), 0004 (packaging — retired). 0001 (MVP foundation).
|
||||
- **Sister repo**: phase-memory (profile execution, phases: ephemeral/fluid/stabilized/rigid, planners for lifecycle/activation/compaction/synthesis, MemoryGraphStore/EventLog/PolicyGateway ports, Markitect interop). cya must provide clear, prioritized feedback on what it needs from that layer.
|
||||
|
||||
This workplan turns the research into an executable, reviewable plan while keeping the first slice deliberately scoped (baseline + definitions + high-quality feedback artifact + minimal Profile 1 skeleton if capacity allows).
|
||||
|
||||
## Non-Goals (for this workplan)
|
||||
|
||||
- Full implementation of Profiles 2 and 3 (spikes / design only; implementation may be later slices or owned by phase-memory).
|
||||
- Automatic / background synthesis or meta-reflection without explicit user trigger or dry-run review.
|
||||
- Changes to the rule-based RiskClassifier safety core (memory influence remains strictly additive for caution).
|
||||
- Deep embedding / semantic index work (phase-memory concern).
|
||||
- PyPI release or CI automation (already registered as debt in 0004).
|
||||
- Activation of this workplan or ralph-loop execution (create proposed, register, sync; activation is a later operator decision).
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Persist research and establish shared vocabulary
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "69f8a82d-4e3d-4590-bdf9-d59a5fff00c6"
|
||||
started: "2026-05-28 ralph iter 1"
|
||||
completed: "2026-05-28 ralph iter 1"
|
||||
```
|
||||
|
||||
**Description**: Ensure the deep agentic memory research is first-class and referenceable. Create (or confirm) the persisted research document and add a short "Agentic Memory Research" section to MemoryVision.md that introduces the three profile directions with links.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` exists, is complete, and contains the three variations, cya mappings, Profile 0 baseline description, and phase-memory suggestions (already produced in pre-workplan step).
|
||||
- MemoryVision.md gains a clear "Research Foundation & Profile Directions (2026-05)" section (or equivalent) that references the history doc and the three variations.
|
||||
- No duplication of long research text in MemoryVision — it points to the history artifact.
|
||||
|
||||
**Deliverables**: Updated MemoryVision.md (small additive section), confirmation that research doc is committed.
|
||||
|
||||
### T02 — Formalize Profile 0 as the explicit shipped baseline
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "669c0d9e-f2ba-4655-a0b9-c104f5545859"
|
||||
started: "2026-05-28 ralph iter 2"
|
||||
completed: "2026-05-28 ralph iter 2"
|
||||
```
|
||||
|
||||
**Description**: Treat the current post-0003 memory implementation (local JSON, kinds, activation_context, retrospection helper, provenance, safety integration, `cya retrospect`) as **Profile 0**. Document it clearly so Profiles 1–3 have a stable "from here" point. Update all relevant docs and code headers.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- MemoryVision.md contains a new "Profile 0 Baseline (Post-0003)" section that exactly describes the current ports, backing store, activation logic, retrospection flow, safety invariants, and explainability surface (pull from research doc + src/cya/memory + orchestrator).
|
||||
- SCOPE.md "Current Delivered Scope" and "Integrates With" sections are updated to name "Profile 0 (contextual activation + retrospection loop on local JSON)" as delivered capability.
|
||||
- `src/cya/memory/__init__.py` and `orchestrator.py` gain short header comments: "Profile 0 implementation — see CYA-WP-0005 and MemoryVision for evolution to 1–3."
|
||||
- Tests (test_memory.py) explicitly assert Profile 0 behaviors (kinds, activation_context boost, retrospection roundtrip, by_kind export, provenance in results).
|
||||
- README and AGENTS.md mention "Current memory profile: 0 (real contextual + retrospection; Profiles 1–3 planned)" with link to the workplan.
|
||||
|
||||
**Deliverables**: Updated MemoryVision, SCOPE, source headers, tests, README/AGENTS. Clear, testable definition of Profile 0.
|
||||
|
||||
### T03 — Define Profiles 1–3 with cya-specific integration plans
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6f292b8b-b17d-4495-99b3-db031f6fb339"
|
||||
started: "2026-05-28 ralph iter 3"
|
||||
completed: "2026-05-28 ralph iter 3"
|
||||
```
|
||||
|
||||
**Description**: Turn the research synthesis into concrete profile definitions, each with: intent, core loop, mapping to existing cya ports/kinds/orchestrator/retrospect/risk, delta required, phase-memory dependencies, safety/explainability notes, and acceptance criteria sketch. Include a simple profile selection / capability matrix.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- MemoryVision.md (or a new `docs/memory-profiles.md`) contains crisp definitions for Profiles 1 (Reflexion verbal self-improvement), 2 (hierarchical synthesis), 3 (procedural/meta-policy), each with the elements above and direct references to the 2026-05-28 research doc.
|
||||
- Workplan itself contains the same definitions in an appendix or "Profile Definitions" section for standalone readability.
|
||||
- A one-page "Profile Capability Matrix" (table) shows for each profile: primary new kinds, activation enhancements, synthesis needs, procedural support, user control surface, safety impact, estimated implementation cost for cya vs phase-memory.
|
||||
- No implementation code yet (except minimal scaffolding if it falls out of T04); definitions and docs only for this task.
|
||||
|
||||
**Deliverables**: Rich profile definitions in MemoryVision + workplan, capability matrix, clear handoff points for phase-memory.
|
||||
|
||||
### T04 — Produce Optimization Suggestions & Missing Functionality feedback for phase-memory
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d5bc201c-ba10-4031-87fd-fe8207cdee8f"
|
||||
started: "2026-05-28 ralph iter 4"
|
||||
completed: "2026-05-28 ralph iter 4"
|
||||
```
|
||||
|
||||
**Description**: Extract, polish, and package the "Optimization Suggestions & Missing Functionality for phase-memory" content (already drafted in the research doc) into a clean, reviewable, sister-repo-targeted deliverable. This is one of the primary outputs of the workplan.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- A focused artifact exists: `docs/phase-memory-optimization-suggestions.md` (or `history/2026-05-28-CYA-phase-memory-Feedback.md`) containing prioritized, actionable items in the 9 categories from the research (refined ports, synthesis/planner hooks, procedural support, activation/retrieval, lifecycle/phase, explainability/audit, safety/policy integration, observability, retrospection interop).
|
||||
- Each item states: what cya needs, why (which profile or invariant it enables), suggested interface shape or behavior, and priority (must-have for Profile 1, nice for 2, required for 3).
|
||||
- The document is written for phase-memory / markitect owners (clear, non-cya-centric language where possible, references the MemoryVision contract).
|
||||
- Workplan and MemoryVision reference the feedback doc as the official coordination artifact.
|
||||
- (Optional but recommended) A short summary table or "Top 5 asks" is included for quick reading in State Hub messages or cross-repo sync.
|
||||
|
||||
**Deliverables**: Standalone, high-quality feedback document ready for handoff to the phase-memory team. This directly fulfills the user request "For interface optimization and missing functionality in phase-memory provide optimization suggestions to the sister repo."
|
||||
|
||||
### T05 — Minimal Profile 1 implementation spike (Reflexion verbal) — optional but recommended
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T05
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4b59d4f4-d067-470b-b6c3-30c64cbc1010"
|
||||
started: "2026-05-28 ralph iter 5 (optional spike)"
|
||||
completed: "2026-05-28 ralph iter 5"
|
||||
```
|
||||
|
||||
**Description**: If time and review allow, implement a thin but real end-to-end slice of Profile 1 on top of Profile 0: new `kind="reflection"` support (or reuse retrospection), enhancement to `cya retrospect` (or new `cya reflect`) to capture verbal lessons, preferential activation for reflection kinds, rendering in `--explain-context` and final output, tests, and docs. All changes must be small, inspectable, and preserve safety invariants.
|
||||
|
||||
**Acceptance criteria** (if executed):
|
||||
- `remember_reflection` or extended `remember_retrospection_outcome` path works and stores with the new kind.
|
||||
- `recall_preferences(..., kinds=["reflection", ...])` + activation_context returns them preferentially.
|
||||
- `cya retrospect` (or a new subcommand) offers a "capture lesson / reflection" step whose output is stored and later activated.
|
||||
- `--explain-context` and normal responses surface "X verbal reflections influenced this" with the actual text when relevant.
|
||||
- All new memory still flows through RiskClassifier (can only add caution).
|
||||
- Full roundtrip tests + README example.
|
||||
- If the spike grows beyond a small delta, it is carved into its own follow-on workplan and this task is marked partial + blocked.
|
||||
|
||||
**Deliverables**: Working (if minimal) Profile 1 behavior that users can exercise today, or a clear "spike complete, production slice needs X" note.
|
||||
|
||||
### T06 — Update tests, observability, and safety surface for profile evolution
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "9e9566c5-ce76-4e73-b789-f07eb5fd54fb"
|
||||
started: "2026-05-28 ralph (satisfied by T05 spike + T02/T03)"
|
||||
completed: "2026-05-28 ralph"
|
||||
```
|
||||
|
||||
**Description**: Ensure the test suite and explainability mechanisms treat profiles as first-class and that safety invariants are explicitly asserted for any memory that could influence future profiles.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- `tests/test_memory.py` (and any new profile tests) cover: profile param passthrough, kind filtering/boosting, activation_context with profile, export by_kind including new reflection/procedural kinds, provenance on all recall/export results.
|
||||
- Risk classifier tests (or new integration) assert that memory records with high-risk keywords or from procedural kinds still force confirmation and never downgrade risk level.
|
||||
- `--explain-context` output (or the ContextEnvelope) includes profile id and a "memory profile influence" section when non-default.
|
||||
- No new auto-execution or hidden memory paths introduced.
|
||||
|
||||
**Deliverables**: Green tests that will continue to pass as Profiles 1–3 are added; explicit safety assertions for the profile machinery.
|
||||
|
||||
### T07 — Documentation, examples, and user-facing story
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T07
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "4c42b6f8-955e-442e-ae31-83d794bda304"
|
||||
started: "2026-05-28 ralph (satisfied by T03/T05 docs + README/AGENTS updates)"
|
||||
completed: "2026-05-28 ralph"
|
||||
```
|
||||
|
||||
**Description**: Make the new profile model discoverable and usable for the primary operator and future contributors.
|
||||
|
||||
**Acceptance criteria**:
|
||||
- README "Memory" or "Advanced" section describes Profile 0 today + the roadmap to 1–3 with links to the workplan and research doc.
|
||||
- AGENTS.md updated with the new workplan reference and any new dev commands (e.g. running retrospect with synthesis).
|
||||
- At least one concrete example in docs/ or README showing a user exercising Profile 0 (or 1 if T05 landed) and seeing the self-improvement loop in action.
|
||||
- MemoryVision.md "Success Criteria" or "Profile-Driven Behavior" section is refreshed to speak in profile terms.
|
||||
|
||||
**Deliverables**: Updated public docs so a new reader understands where memory is today and where it is headed.
|
||||
|
||||
### T08 — Register, commit, sync with State Hub, and close the workplan slice
|
||||
|
||||
```task
|
||||
id: CYA-WP-0005-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "fb7b17e5-a22e-465e-8f98-6d2bbdf0059d"
|
||||
started: "2026-05-28 ralph final close (prior registration + commits + events satisfy most criteria)"
|
||||
completed: "2026-05-28 ralph"
|
||||
```
|
||||
|
||||
**Description**: Follow AGENTS.md exactly: commit all artifacts (research doc, workplan, suggestions doc, doc updates), create/register the workstream in State Hub for the topic, seed `state_hub_workstream_id` (and task ids if returned) into the Markdown frontmatter and task blocks, run `make fix-consistency REPO=can-you-assist` until it reports ✓ PASS (self-execute per prior "do that yourself" authorization pattern), log a progress event, and mark the workplan frontmatter `status: ready` or `proposed` per operator preference (recommended: proposed until review).
|
||||
|
||||
**Acceptance criteria**:
|
||||
- Git commit(s) exist with clear messages referencing CYA-WP-0005 and the user request.
|
||||
- State Hub workstream exists for topic `64418556-3206-457a-ba29-6884b5b12cf3` with title matching the workplan; id is written into the .md.
|
||||
- `make fix-consistency REPO=can-you-assist` exits with RESULT: ✓ PASS (warnings on description drift or closed bootstrap items are acceptable and noted).
|
||||
- `.custodian-brief.md` is regenerated with the new workplan visible (or noted as ready/proposed).
|
||||
- A progress event is POSTed to State Hub with summary of the slice (research persisted + workplan created + feedback prepared for phase-memory).
|
||||
- Workplan frontmatter status is set appropriately (proposed or ready); no tasks are left in "in_progress" without justification.
|
||||
- Operator is reminded (in final output) to run the consistency make if the assistant did not self-execute it.
|
||||
|
||||
**Deliverables**: Fully registered, synced, committed workplan + artifacts; State Hub and brief reflect the new planning artifact; clean handoff for future activation or ralph-loop.
|
||||
|
||||
## Dependencies & Cross-Repo Coordination
|
||||
|
||||
- **phase-memory (markitect)**: Primary consumer of the T04 feedback deliverable. The suggestions doc + MemoryVision contract (already shared) + this workplan should be the coordination package. State Hub messages or direct handoff recommended after T04/T08.
|
||||
- **State Hub**: Workstream + task tracking, progress events, decisions (if any profile policy choices arise). Use for cross-domain visibility (capabilities ↔ markitect).
|
||||
- No hard runtime dependency on llm-connect for this planning slice.
|
||||
|
||||
## Debt & Future Work (Registered)
|
||||
|
||||
This workplan explicitly registers the following as first-class items for later slices or sister-repo ownership:
|
||||
|
||||
- Full implementation of Profile 2 (hierarchical synthesis + compaction) and Profile 3 (procedural evolution + meta-reflection UX).
|
||||
- Profile selection / switching UX (`cya memory profile ...` or config file) and per-request profile hints.
|
||||
- Automatic (background or low-load) synthesis triggers vs purely user-triggered.
|
||||
- Rollback / versioning for procedural memory changes.
|
||||
- Integration of profile-driven memory evolution proposals into the rule-based risk surface (so a proposed rule change can be risk-assessed before user review).
|
||||
- Deeper phase-memory wiring: replace local JSON backing with real graph/event store while preserving the exact same port signatures and explainability surface.
|
||||
- PyPI / release of a version that includes Profile 1 (or higher) — builds on 0004 debt.
|
||||
- Long-term: cya participation in phase-memory planner dry-runs for activation and compaction budgets.
|
||||
|
||||
These will be tracked via State Hub (this workplan's tasks + future ADRs or child workplans).
|
||||
|
||||
## Success Criteria (for the workplan as a whole)
|
||||
|
||||
When complete:
|
||||
- Anyone (operator, future contributor, phase-memory owner) can read the research doc + this workplan + MemoryVision and understand exactly what "memory profile 0" is today and what 1–3 will add.
|
||||
- A high-quality, actionable feedback package exists for the phase-memory sister repo that directly enables the three self-improving variations.
|
||||
- The seam (ports, kinds, activation_context, provenance) is proven stable enough to support profile evolution without breaking changes.
|
||||
- All changes are small, inspectable, tested, safe, and committed + synced per AGENTS protocol.
|
||||
|
||||
---
|
||||
|
||||
**Status note**: Completed 2026-05-28. Profile 0 baseline, Profiles 1–3 definitions, phase-memory
|
||||
feedback artifact, and minimal Profile 1 spike are delivered. Production hardening for Profile 1
|
||||
is tracked in `workplans/CYA-WP-0006-profile-1-production-hardening.md`.
|
||||
|
||||
**References** (non-exhaustive):
|
||||
- Research: `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md`
|
||||
- Memory contract: MemoryVision.md (esp. 0002 T01 integration contract)
|
||||
- Activation/retrospection concept: `docs/cya-memory-activation-and-retrospection-concept.md`
|
||||
- Post-0004 assessment: `history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0004.md`
|
||||
- Prior workplans: 0002, 0003 (memory foundation), 0004 (packaging)
|
||||
- External: Shinn Reflexion (2023), Park Generative Agents (2023), LangMem / A-Mem / MemGPT literature (see research doc for links)
|
||||
|
||||
This completes the planning artifact requested in the 2026-05-28 user query.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Profile Definitions Reference (T03)
|
||||
|
||||
The authoritative, living definitions for Profiles 0–3 (including the Capability Matrix) live in **MemoryVision.md** under the sections:
|
||||
|
||||
- "Profile 0 Baseline (Post-0003 / Current Shipped)"
|
||||
- "Profiles 1–3: Definitions and cya Integration Plans" (with detailed intent, core loops, cya mappings, phase-memory fit, safety notes, and the full matrix table)
|
||||
|
||||
These were added during T03 and are the single source of truth for the self-improving memory model. The research artifact `history/2026-05-28-CYA-Agentic-Memory-Research-Variations.md` remains the deep background with citations.
|
||||
216
workplans/CYA-WP-0006-profile-1-production-hardening.md
Normal file
216
workplans/CYA-WP-0006-profile-1-production-hardening.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
id: CYA-WP-0006
|
||||
type: workplan
|
||||
title: "Profile 1 Production Hardening: Reflection UX, Compaction, and Surfacing"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: finished
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-06-19"
|
||||
updated: "2026-06-22"
|
||||
state_hub_workstream_id: "f62c6908-dec0-442c-83d2-e34f0e87c1e7"
|
||||
---
|
||||
|
||||
# CYA-WP-0006: Profile 1 Production Hardening
|
||||
|
||||
## Goal
|
||||
|
||||
Move the **Profile 1** (Reflexion-style verbal reflections) spike from CYA-WP-0005-T05
|
||||
from "minimal but working" to **production-quality** behavior that users can rely on daily:
|
||||
|
||||
- Clearer capture UX inside `cya retrospect`
|
||||
- Lightweight reflection management (review, edit path, basic compaction)
|
||||
- Stronger surfacing in normal responses and `--explain-context`
|
||||
- Expanded tests and observability
|
||||
|
||||
Preserve all Profile 0 / safety invariants: user-controlled inspectable memory, full
|
||||
provenance, memory signals add caution only (never downgrade risk or bypass confirmation).
|
||||
|
||||
## Background & References
|
||||
|
||||
- **Profile 1 spike (done):** `remember_reflection()`, `KIND_REFLECTION`, optional capture
|
||||
step in `run_retrospection()`, preferential recall by kind, basic output surfacing.
|
||||
- **Gap analysis:** `history/2026-05-28-CYA-Intent-Scope-Gap-Analysis-Post-0005.md` —
|
||||
recommends production-hardening Profile 1 as the highest-leverage next deepening step.
|
||||
- **Profile definitions:** MemoryVision.md — Profile 1 section and Capability Matrix.
|
||||
- **Safety contract:** `src/cya/safety/risk.py` + CYA-WP-0002-T04 invariants.
|
||||
- **Gap checklist (T01):** `docs/CYA-WP-0006-profile-1-gap-checklist.md`
|
||||
|
||||
## Non-Goals (for this slice)
|
||||
|
||||
- Profile 2 (hierarchical synthesis) or Profile 3 (procedural evolution) implementation.
|
||||
- Real `llm-connect` client wiring (separate future slice).
|
||||
- Deep `phase-memory` graph/planner integration (feedback doc exists; wiring is later).
|
||||
- Automatic background reflection generation without explicit user trigger.
|
||||
- PyPI publishing or CI automation (registered debt from CYA-WP-0004).
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Audit Profile 1 spike gaps vs MemoryVision acceptance
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "ec8cc24d-80ca-4a51-b98c-87d0cfc9a110"
|
||||
```
|
||||
|
||||
Document the delta between shipped spike behavior and MemoryVision Profile 1 acceptance
|
||||
criteria: capture UX, activation surfacing, compaction, explainability, safety integration.
|
||||
Produce a short checklist in the workplan or `docs/` that gates T02–T05.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Gap checklist exists with prioritized items mapped to tasks T02–T05.
|
||||
- No code changes required unless a blocking bug is found (file separately as ADHOC if so).
|
||||
|
||||
**Done:** `docs/CYA-WP-0006-profile-1-gap-checklist.md`
|
||||
|
||||
### T02 — Improve `cya retrospect` reflection capture UX
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "c7f381d4-d362-4183-a7bd-d3ceea7e997d"
|
||||
```
|
||||
|
||||
Enhance the optional verbal-lesson step in `run_retrospection()`:
|
||||
|
||||
- Guided prompts (what went well / what to remember / what to avoid)
|
||||
- Preview before save; allow skip without storing empty records
|
||||
- Store structured metadata (session date, scope) in provenance
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Users can capture 1–3 concise lessons with clear prompts and confirmation.
|
||||
- Skipping leaves no orphan/empty reflection records.
|
||||
- Existing retrospection kinds and goals flow unchanged.
|
||||
|
||||
**Done:** `_capture_reflection_lessons()` in `orchestrator.py`; helpers in `memory/reflections.py`.
|
||||
|
||||
### T03 — Reflection review and lightweight compaction
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "078d6f17-6d56-42ec-85c9-140c41d7e83f"
|
||||
```
|
||||
|
||||
Add user-visible reflection management without hiding state:
|
||||
|
||||
- `export_memory(..., kinds=["reflection"])` surfaced via CLI helper or documented path
|
||||
- Simple compaction: detect near-duplicate reflections in the same scope (string similarity
|
||||
or normalized key collision) and offer merge/replace in retrospect or a small subcommand
|
||||
- All compaction is explicit — no silent deletion
|
||||
|
||||
**Acceptance criteria:**
|
||||
- User can list/export reflections for a scope.
|
||||
- Duplicate detection works on a small fixture set; merge/replace is opt-in.
|
||||
- Compaction never bypasses safety or provenance requirements.
|
||||
|
||||
**Done:** `cya memory reflections` CLI; `_offer_reflection_compaction()` in retrospect; `compact_reflections()` / `find_duplicate_reflection_groups()`.
|
||||
|
||||
### T04 — Strengthen surfacing in responses and `--explain-context`
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b1f7a333-bf9a-478c-993b-e421524ced3a"
|
||||
```
|
||||
|
||||
Improve how activated reflections appear in `handle_request()` and context explanation:
|
||||
|
||||
- Show count + truncated lesson text in `--explain-context` with provenance
|
||||
- Normal responses include a concise "reflections influenced this" line when relevant
|
||||
- Cap token/line budget to avoid wall-of-text
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Roundtrip test: stored reflection → recall → visible in explain output.
|
||||
- Output remains readable for 0, 1, and 5+ reflections.
|
||||
|
||||
**Done:** `format_reflection_surfacing()`; recall prioritization for `KIND_REFLECTION`.
|
||||
|
||||
### T05 — Tests, observability, and safety regression coverage
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b42c8fa1-6ab7-4b75-bdd5-43006e2d0a9c"
|
||||
```
|
||||
|
||||
Expand `tests/test_memory.py` and orchestrator tests for:
|
||||
|
||||
- New capture UX paths (prompt/skip/preview)
|
||||
- Compaction opt-in behavior
|
||||
- Surfacing in explain-context
|
||||
- Safety invariant: reflections cannot downgrade destructive-command confirmation
|
||||
|
||||
Add basic observability in `export_memory` (reflection counts by scope).
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `make test` / `python3 -m pytest tests/ -q` passes cleanly.
|
||||
- At least one test per new behavior path from T02–T04.
|
||||
|
||||
**Done:** 9 new tests in `test_memory.py`; `tests/test_orchestrator.py` added.
|
||||
|
||||
### T06 — Documentation updates
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "18498a09-1d1c-424b-bf13-6952fabd34d3"
|
||||
```
|
||||
|
||||
Update README.md (retrospect / Profile 1 section), MemoryVision.md status note, and
|
||||
SCOPE.md if delivered scope changes materially.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- README documents the hardened Profile 1 flow with an example session.
|
||||
- MemoryVision notes Profile 1 as "production" (not "spike") when T02–T05 complete.
|
||||
|
||||
**Done:** README, MemoryVision, SCOPE updated.
|
||||
|
||||
### T07 — Register, sync, and handoff
|
||||
|
||||
```task
|
||||
id: CYA-WP-0006-T07
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "a9c2627f-7ed8-45a9-b1ee-7ba64ebbcd09"
|
||||
```
|
||||
|
||||
Register workstream in State Hub via `make fix-consistency REPO=can-you-assist`,
|
||||
log progress event, set workplan to `ready` after review or `active` when implementation
|
||||
starts.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `state_hub_workstream_id` and task ids written by fix-consistency.
|
||||
- `.custodian-brief.md` regenerated with CYA-WP-0006 visible.
|
||||
|
||||
## Dependencies & Cross-Repo Coordination
|
||||
|
||||
- **phase-memory:** Optional future compaction planner hooks — not required for this slice.
|
||||
See `docs/phase-memory-optimization-suggestions.md` for long-term asks.
|
||||
- **llm-connect:** Not required (reflections are user-authored text in this slice).
|
||||
|
||||
## Debt & Future Work (Registered)
|
||||
|
||||
- Profile 2 synthesis spikes (user-triggered, dry-run first).
|
||||
- Real `llm-connect` adapter implementation slice.
|
||||
- CI gate for `make test` + `make check-dist` (from CYA-WP-0004 debt).
|
||||
- Profile selection UX (`cya memory profile ...`).
|
||||
|
||||
## Success Criteria
|
||||
|
||||
When complete:
|
||||
|
||||
- Profile 1 is demonstrably production-usable: capture, review, activation, and surfacing
|
||||
are polished and tested.
|
||||
- Safety and explainability invariants from Profile 0 remain intact.
|
||||
- Users reading README + MemoryVision understand Profile 1 as shipped capability, not a spike.
|
||||
|
||||
**Completed 2026-06-22.** All tasks done; 36 tests pass.
|
||||
281
workplans/CYA-WP-0007-interactive-shell-session.md
Normal file
281
workplans/CYA-WP-0007-interactive-shell-session.md
Normal file
@@ -0,0 +1,281 @@
|
||||
---
|
||||
id: CYA-WP-0007
|
||||
type: workplan
|
||||
title: "Interactive Shell Session: REPL, History Context, and Hub-Aware Dev-Sec-Ops Helper"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: finished
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-06-22"
|
||||
updated: "2026-06-23"
|
||||
state_hub_workstream_id: "449b820c-6a7d-4b2f-93d8-f742aba45eab"
|
||||
---
|
||||
|
||||
# CYA-WP-0007: Interactive Shell Session
|
||||
|
||||
## Goal
|
||||
|
||||
Add an interactive `cya shell` mode — a console session comparable to starting Grok or
|
||||
Claude Code — that:
|
||||
|
||||
- Maintains lightweight in-session state across turns
|
||||
- Optionally incorporates **opt-in, capped, redacted** shell history for continuity
|
||||
- Orients on **State Hub** workstreams and inbox at session start (read-only by default)
|
||||
- Reuses the existing orchestrator, safety, and Profile 0/1 memory seams
|
||||
- Supports end-of-session learning (retrospect, reflections, episodic export)
|
||||
- Surfaces rule-based **weakness hints** (credential anti-patterns, repeated risk) for
|
||||
knowledge transfer — never auto-executes or downgrades safety
|
||||
|
||||
`cya` remains a helper **alongside** the shell, not a replacement for it.
|
||||
|
||||
## Background & References
|
||||
|
||||
- **INTENT.md:** interaction history under user control; recent command history when permitted.
|
||||
- **SCOPE.md (current):** REPL and shell history explicitly deferred — this workplan
|
||||
proposes a scoped update in T01.
|
||||
- **CYA-WP-0006 (done):** Profile 1 reflections + `cya retrospect` — end-of-session learning.
|
||||
- **wiki/CyaSpeechModeExtension.md:** prior session-mode thinking (voice bridge later).
|
||||
- **Collector contract:** `src/cya/context/collector.py` — history excluded until opt-in.
|
||||
- **State Hub:** non-runtime per `repo-boundary.md`; HTTP read at session start, write on
|
||||
explicit operator action only.
|
||||
|
||||
## Non-Goals (for this slice)
|
||||
|
||||
- Autonomous or background command execution
|
||||
- Full phase-memory graph / Profile 2 synthesis
|
||||
- Voice / phone bridge (see wiki extension doc)
|
||||
- Team-shared or hosted session state
|
||||
- Deep semantic repo indexing or embeddings
|
||||
- CI / PyPI automation
|
||||
- Replacing State Hub MCP or custodian workflows
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **CYA-WP-0008** (llm-connect adapter): required for *conversational quality* in the REPL;
|
||||
CYA-WP-0007 MVP (T02–T05) can ship with `FakeLLMAdapter` for scaffolding and
|
||||
history/hub/explain paths.
|
||||
- **llm-connect** repo: stable client behind `LLMAdapter` Protocol.
|
||||
- **State Hub API** at `http://127.0.0.1:8000` (or tunnel) for hub orientation tasks.
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Design doc and SCOPE alignment
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "b91c7b42-5ad9-4dcd-b237-63394a0f2f52"
|
||||
```
|
||||
|
||||
Produce `docs/cya-interactive-shell-session-design.md` covering:
|
||||
|
||||
- REPL UX (`cya shell`), slash commands, session file layout
|
||||
- Shell history opt-in model (config + flag), caps, redaction rules, provenance
|
||||
- State Hub read/write boundaries and slash commands
|
||||
- Episodic session log schema (`kind: session_turn` precursor)
|
||||
- Weakness-hint rule catalog (v1, deterministic)
|
||||
- SCOPE.md update: move REPL from out-of-scope to owned capability
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Design doc reviewed against INTENT, SCOPE, MemoryVision, repo-boundary.
|
||||
- Explicit checklist gates T02–T08.
|
||||
|
||||
### T02 — REPL skeleton and session state
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "6f2361af-312b-4f30-bf2d-17c0ee387d29"
|
||||
```
|
||||
|
||||
Implement `cya shell` Typer subcommand:
|
||||
|
||||
- Readline or `prompt_toolkit` loop with prompt `cya> `
|
||||
- Session id + user-owned artifact at `~/.config/cya/sessions/<id>.jsonl`
|
||||
- In-session turn buffer (user + assistant text, timestamps)
|
||||
- Slash commands: `/exit`, `/help`, `/explain` (last turn context)
|
||||
- Graceful handling of EOF, Ctrl-C, non-TTY
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `cya shell` starts, accepts input, persists turns to jsonl, exits cleanly.
|
||||
- No LLM required for loop mechanics (may echo or use FakeLLMAdapter).
|
||||
|
||||
### T03 — Opt-in shell history collector
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T03
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0bce247e-d829-46eb-a8b0-63848bf9dfd5"
|
||||
```
|
||||
|
||||
Extend context collection (new module or `collector` extension):
|
||||
|
||||
- Opt-in via `cya shell --with-history` and/or `[shell_history]` in config TOML
|
||||
- Read last N lines from `$HISTFILE` or shell-appropriate fallback (`fc -l`)
|
||||
- Hard cap (default 50 lines), redact secrets (API keys, tokens, passwords)
|
||||
- Provenance per line in context envelope; visible via `/explain`
|
||||
- Default **off** — no history without explicit enable
|
||||
|
||||
**Acceptance criteria:**
|
||||
- History appears in explain output when enabled; absent when disabled.
|
||||
- Redaction tested with fixture lines containing fake secrets.
|
||||
- Collector core invariants preserved for one-shot `cya "..."` (history off by default).
|
||||
|
||||
### T04 — State Hub session orientation
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "dc468c7e-92a8-48fb-bb10-06cd5da72e4d"
|
||||
```
|
||||
|
||||
At session start, load orientation (graceful if hub offline):
|
||||
|
||||
- `.custodian-brief.md` when present
|
||||
- Active workstreams for topic `64418556-3206-457a-ba29-6884b5b12cf3`
|
||||
- Unread inbox for `to_agent=can-you-assist`
|
||||
|
||||
Slash commands:
|
||||
|
||||
- `/hub` — show active workstreams + open tasks for current repo
|
||||
- `/hub log "summary"` — POST progress event (operator confirms)
|
||||
- `/inbox` — show unread messages (mark read only after explicit action)
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Session banner shows brief summary when hub reachable.
|
||||
- Hub offline degrades gracefully; shell still usable.
|
||||
- No automatic hub writes without confirmation.
|
||||
|
||||
### T05 — Wire REPL through orchestrator and safety
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "5e2f2775-56b2-4f23-a2a9-66c6b26dde16"
|
||||
```
|
||||
|
||||
Connect each REPL turn to existing pipeline:
|
||||
|
||||
- `collect()` + optional history + memory recall + `classify()` + adapter + render
|
||||
- Mandatory confirmation for non-safe levels (same as one-shot)
|
||||
- Memory surfacing (Profile 1 reflections) in multi-turn output
|
||||
- `/explain` shows full envelope for last turn
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Destructive intent in REPL still requires confirmation; memory cannot bypass.
|
||||
- Turn output consistent with one-shot `cya "..."` for same input.
|
||||
|
||||
### T06 — End-of-session learning and export
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "7ad8ae85-5ba8-40e3-9c9a-facf918f2b16"
|
||||
```
|
||||
|
||||
On `/exit` or session end:
|
||||
|
||||
- Offer inline Profile 1 lesson capture (reuse `memory/reflections.py` helpers)
|
||||
- Optional `cya retrospect`-style mini-flow without leaving shell history
|
||||
- `/export-session` — write redacted session summary JSON for knowledge transfer
|
||||
- Store episodic turns with `kind: session_turn` (new constant) when user confirms
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Session export is user-triggered and inspectable.
|
||||
- Reflections from shell session appear in `cya memory reflections`.
|
||||
- Skip path leaves no orphan records.
|
||||
|
||||
### T07 — Weakness hints (rule-based v1)
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T07
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "fbdd4f04-76ef-4a18-8c40-b41a134a743b"
|
||||
```
|
||||
|
||||
Deterministic post-turn or end-session hints:
|
||||
|
||||
- Credential anti-patterns in discussed commands (paste keys, `warden route` bypass)
|
||||
- Repeated destructive suggestions without confirmation
|
||||
- State Hub alignment warnings (active work not matching stated goal)
|
||||
|
||||
Surface as informational panels — not auto-fixes. Optional save as reflection.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- At least three hint rules with tests.
|
||||
- Hints never change risk level or skip confirmation.
|
||||
|
||||
### T08 — Tests, docs, and operator guide
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T08
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "3bed6582-a11e-462a-843a-271c842b0103"
|
||||
```
|
||||
|
||||
- `tests/test_shell_session.py` — REPL loop, history opt-in, hub degrade, safety
|
||||
- README section: starting a shell session, history opt-in, hub commands
|
||||
- AGENTS.md command reference update
|
||||
- Example operator session in docs
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `make test` passes.
|
||||
- README documents `cya shell` with example transcript.
|
||||
|
||||
### T09 — Register, sync, and handoff
|
||||
|
||||
```task
|
||||
id: CYA-WP-0007-T09
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "3c932e69-ee0a-4135-be11-f890da09509d"
|
||||
```
|
||||
|
||||
Run `make fix-consistency REPO=can-you-assist`, log progress, move to `active` when
|
||||
implementation begins.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
When complete:
|
||||
|
||||
- Operators can run `cya shell`, pick up recent shell context (opt-in), see hub work,
|
||||
get multi-turn assistance with full safety, and export learnings for transfer.
|
||||
- All artifacts remain user-controlled and explainable.
|
||||
- SCOPE reflects interactive session as an owned capability.
|
||||
|
||||
---
|
||||
|
||||
**Status note:** Promoted to `ready` on 2026-06-22 after operator approval of direction.
|
||||
Pair with CYA-WP-0008 for production LLM quality in the REPL.
|
||||
|
||||
## Completion Note - 2026-06-23
|
||||
|
||||
Implemented in this repo by Codex. Delivered `cya shell` with local JSONL
|
||||
session artifacts, optional capped/redacted shell history, State Hub orientation
|
||||
and explicit hub slash-command writes, `/explain`, `/export-session`, end-session
|
||||
Profile 1 learning hooks, opt-in `session_turn` memory persistence, deterministic
|
||||
weakness hints, docs, and tests.
|
||||
|
||||
Verification:
|
||||
|
||||
```bash
|
||||
make test
|
||||
python3 -m cya.cli.main --offline hello
|
||||
python3 -m cya.cli.main shell --help
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Operator follow-up: after this workplan file change, run from `~/state-hub`:
|
||||
|
||||
```bash
|
||||
make fix-consistency REPO=can-you-assist
|
||||
```
|
||||
179
workplans/CYA-WP-0008-llm-connect-adapter-integration.md
Normal file
179
workplans/CYA-WP-0008-llm-connect-adapter-integration.md
Normal file
@@ -0,0 +1,179 @@
|
||||
---
|
||||
id: CYA-WP-0008
|
||||
type: workplan
|
||||
title: "llm-connect Adapter Integration for Production Assistance"
|
||||
domain: agents
|
||||
repo: can-you-assist
|
||||
status: finished
|
||||
owner: grok
|
||||
topic_slug: foerster-capabilities
|
||||
created: "2026-06-22"
|
||||
updated: "2026-06-22"
|
||||
state_hub_workstream_id: "f713db12-5b90-4453-8fbb-a0e50f61699b"
|
||||
---
|
||||
|
||||
# CYA-WP-0008: llm-connect Adapter Integration
|
||||
|
||||
## Goal
|
||||
|
||||
Replace `FakeLLMAdapter` on production code paths with a real **llm-connect** client
|
||||
behind the existing `LLMAdapter` Protocol, so one-shot `cya "..."` and `cya shell`
|
||||
(CYA-WP-0007) can deliver genuinely useful multi-turn assistance.
|
||||
|
||||
Preserve the seam: `cya` never hard-codes a vendor; all provider config lives in
|
||||
llm-connect. Credential routing via `warden route` before requesting secrets.
|
||||
|
||||
## Background & References
|
||||
|
||||
- **Seam:** `src/cya/llm/adapter.py` — `LLMAdapter` Protocol + `FakeLLMAdapter`
|
||||
- **INTENT.md:** `cya` asks; `llm-connect` reaches infrastructure
|
||||
- **SCOPE.md:** real client listed as explicit out-of-scope / future work
|
||||
- **CYA-WP-0007:** REPL usable with fake adapter for scaffolding; quality needs this slice
|
||||
- **Credential routing:** `AGENTS.md` / `warden route find` — no secrets in repo
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- Provider-specific UI inside `cya` (model picker beyond minimal config)
|
||||
- Token billing dashboards or cost analytics
|
||||
- Embedding / RAG pipelines
|
||||
- Bypassing `LLMAdapter` Protocol for any production path
|
||||
|
||||
## Dependencies
|
||||
|
||||
- **llm-connect** package/API stability (coordinate with that repo owner)
|
||||
- Operator-provided credentials via OpenBao paths surfaced by `warden route`
|
||||
- CYA-WP-0007 T05 can proceed in parallel using `FakeLLMAdapter`
|
||||
|
||||
## Task Breakdown
|
||||
|
||||
### T01 — Adapter contract review and llm-connect API survey
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "483d13bb-aabe-48ad-96c2-8df83de5f442"
|
||||
```
|
||||
|
||||
Document mapping from `AssistanceRequest` / `AssistanceResponse` to llm-connect calls.
|
||||
Identify config surface (TOML keys, env vars). Note gaps requiring llm-connect changes.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Short integration note in `docs/` or workplan appendix.
|
||||
- Credential route catalog id(s) documented via `warden route find`.
|
||||
|
||||
**Delivered:** `docs/llm-connect-integration.md`
|
||||
|
||||
### T02 — Implement `LLMConnectAdapter`
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "0fc17ad5-d90b-4ad1-b060-a1a2f9c25ea8"
|
||||
```
|
||||
|
||||
New class in `src/cya/llm/` implementing `LLMAdapter`:
|
||||
|
||||
- Delegates to llm-connect client
|
||||
- Graceful degrade message when llm-connect unavailable / misconfigured
|
||||
- `FakeLLMAdapter` remains for tests and `--offline` mode
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Protocol-compliant; swap via config or env (`CYA_LLM_ADAPTER=connect|fake`).
|
||||
|
||||
**Delivered:** `src/cya/llm/connect_adapter.py`, `src/cya/llm/factory.py`
|
||||
|
||||
### T03 — Configuration and developer ergonomics
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "e8470a37-ecec-42f1-920b-ccd8b98b5512"
|
||||
```
|
||||
|
||||
- `~/.config/cya/config.toml` `[llm]` section (backend, model hints)
|
||||
- Document `warden route` steps in README
|
||||
- `make dev-install` optional extra `[llm]` dependency group if needed
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Operator can configure adapter without editing source.
|
||||
- No secrets committed; example config uses placeholders.
|
||||
|
||||
**Delivered:** `src/cya/config.py`, `docs/cya-config.example.toml`, README section
|
||||
|
||||
### T04 — Orchestrator and shell integration
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T04
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "f2781963-fecf-4576-96de-bd745df271a0"
|
||||
```
|
||||
|
||||
Wire `handle_request()` and CYA-WP-0007 shell turns to adapter selection:
|
||||
|
||||
- Default fake when unconfigured (current behavior)
|
||||
- Real adapter when config present
|
||||
- Multi-turn context: pass recent session turns in `AssistanceRequest.context`
|
||||
|
||||
**Acceptance criteria:**
|
||||
- One-shot and shell paths use same adapter factory.
|
||||
- Session context bounded (token/line budget documented).
|
||||
|
||||
**Delivered:** `get_adapter()` wired in orchestrator; `session_turns` + `bound_session_turns()` ready for CYA-WP-0007 shell
|
||||
|
||||
### T05 — Tests and offline CI strategy
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "32de980b-1a24-4159-9550-7c516570cae3"
|
||||
```
|
||||
|
||||
- Mock llm-connect for unit tests (no live API in default `make test`)
|
||||
- Optional integration test marker `@pytest.mark.llm_live` for manual runs
|
||||
- Fake adapter remains default in CI
|
||||
|
||||
**Acceptance criteria:**
|
||||
- `make test` passes without network or API keys.
|
||||
- Live test documented for operator manual verification.
|
||||
|
||||
**Delivered:** `tests/test_llm_factory.py`, `tests/test_llm_connect_adapter.py`, `tests/test_llm_prompt.py`
|
||||
|
||||
### T06 — Documentation and SCOPE update
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "2d152d4b-e4b2-4a94-8f85-d8f033e55d5f"
|
||||
```
|
||||
|
||||
Update README, SCOPE.md (remove "only FakeLLMAdapter" where accurate), AGENTS.md.
|
||||
|
||||
**Acceptance criteria:**
|
||||
- Clear "configured vs offline" operator paths documented.
|
||||
|
||||
### T07 — Register, sync, and handoff
|
||||
|
||||
```task
|
||||
id: CYA-WP-0008-T07
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "2fb42517-b2df-43d3-8195-f02d310107dc"
|
||||
```
|
||||
|
||||
`make fix-consistency`, progress event, coordinate with llm-connect repo if API gaps found.
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- Configured `cya` uses llm-connect for real inference.
|
||||
- Unconfigured / test environments behave exactly as today (fake adapter).
|
||||
- CYA-WP-0007 shell session becomes practically useful once both workplans complete.
|
||||
|
||||
---
|
||||
|
||||
**Status note:** `finished` on 2026-06-22. Integration doc: `docs/llm-connect-integration.md`.
|
||||
Reference in New Issue
Block a user