Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions openspec/specs/config-system/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
## ADDED Requirements

### Requirement: Centralized Configuration File
The system SHALL load all configuration from a single `config.yaml` file located in the project root directory using YAML parsing.

#### Scenario: System loads config.yaml on startup
- **WHEN** the harness starts
- **THEN** it reads and parses `config.yaml`, merging defaults for any missing sections

#### Scenario: Missing required config fields use defaults
- **WHEN** `config.yaml` omits an optional field such as `telemetry.sampling.ratio`
- **THEN** the system applies the documented default value

### Requirement: LLM Provider Configuration
The system SHALL support configuration of multiple LLM providers including OpenAI-compatible APIs, local model deployments, and custom cloud endpoints, each specifying base URL, model identifier, authentication, rate limits, temperature, and fallback routing.

#### Scenario: User configures an OpenAI-compatible provider
- **WHEN** `config.yaml` contains a provider entry with `type: openai`
- **THEN** the system initializes the provider client using the specified base URL and model

#### Scenario: Provider falls back to second provider on failure
- **WHEN** the primary configured provider returns a consistent error
- **THEN** the system switches to the next provider listed in `fallback_order`

### Requirement: Configuration Validation
The system SHALL validate all `config.yaml` contents against a zod-based schema on boot and on runtime mutation, rejecting invalid configurations with a structured error message.

#### Scenario: Invalid provider config is rejected
- **WHEN** a provider entry is missing a `base_url` field
- **THEN** the system logs a validation error and refuses to initialize that provider

#### Scenario: Runtime config mutation is validated
- **WHEN** the user sets a config value via TUI command (`:config set`)
- **THEN** the system validates the new value against the schema before applying it

### Requirement: Runtime Config Mutation
The system SHALL allow programmatic modification of configuration at runtime via TUI commands or API, enabling dynamic adjustment of sandbox parameters, memory policies, skill permissions, and provider assignments without restarting the harness.

#### Scenario: User changes temperature at runtime
- **WHEN** the user types `:config set inference.temperature 0.7`
- **THEN** the system updates the in-memory config, persists the change to `config.yaml`, and applies it to active provider invocations

#### Scenario: User pauses a skill at runtime
- **WHEN** the user types `:config set skills.<name>.disabled true`
- **THEN** the system removes the skill from the active registry without restarting

### Requirement: Telemetry Configuration
The system SHALL configure OpenTelemetry export settings (format, endpoint, sampling rate, redaction policies) within `config.yaml` under a `telemetry` section.

#### Scenario: User configures OTEL console exporter
- **WHEN** `config.yaml` sets `telemetry.exporter: console`
- **THEN** the system initializes the trace provider with a console exporter and begins emitting spans

#### Scenario: User configures sensitive field redaction
- **WHEN** `config.yaml` sets `telemetry.redact.paths` to include `credentials.apiKey`
- **THEN** all span attributes matching the path are redacted before export
48 changes: 48 additions & 0 deletions openspec/specs/cron-scheduler/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## ADDED Requirements

### Requirement: Declarative Schedule Configuration
The system SHALL read recurring task definitions from a `schedules` section in `config.yaml`, each specifying a cron expression, skill name, input parameters, and optional memory context file.

#### Scenario: User defines a daily health check schedule
- **WHEN** `config.yaml` contains a schedule entry with cron `0 9 * * *`, skill `host-info`, and input `{}`
- **THEN** the system registers a daily task that runs `host-info` at 09:00 local time

#### Scenario: Schedule uses valid cron expression
- **WHEN** `config.yaml` contains a schedule with a syntactically invalid cron expression
- **THEN** the system logs a configuration error and skips the schedule without crashing

### Requirement: Deterministic Sandbox Execution
Each scheduled execution SHALL run within the same sandbox isolation guarantees as user-initiated skill invocations, inheriting sandbox parameters, skill permissions, and memory context from the active session.

#### Scenario: Scheduled skill runs in a sandbox
- **WHEN** a scheduled task triggers at its configured time
- **THEN** the skill executes in a forked process with the configured memory isolation and network restrictions

#### Scenario: Scheduled run inherits memory context
- **WHEN** a schedule entry references a `context_file` in its config
- **THEN** the system loads the referenced memory file and prepends it to the skill's execution context

### Requirement: Concurrency Limits
The system SHALL enforce a maximum concurrent scheduled runs (default: 1) and queue additional runs when the limit is reached, processing them FIFO.

#### Scenario: Second scheduled run waits for first
- **WHEN** two daily schedules trigger at the same time but `max_concurrent` is 1
- **THEN** the second run is queued and begins after the first completes

### Requirement: Schedule Management Commands
The system SHALL expose TUI commands for interacting with the scheduler: `:schedule list`, `:schedule pause <name>`, `:schedule resume <name>`, and `:schedule run-now <name>`.

#### Scenario: User lists active schedules
- **WHEN** the user types `:schedule list`
- **THEN** the system displays all configured schedules with their next run times and enabled status

#### Scenario: User runs a schedule manually
- **WHEN** the user types `:schedule run-now daily-report`
- **THEN** the system immediately executes the `daily-report` schedule entry and logs the result

### Requirement: Schedule Results Logging
Every scheduled execution SHALL log its result as a markdown file in `memory/schedules/` with YAML frontmatter containing the schedule name, cron expression, start time, end time, exit status, and stdout/stderr output.

#### Scenario: Scheduled execution writes output to memory
- **WHEN** a scheduled skill completes
- **THEN** a markdown file is created in `memory/schedules/` documenting the run
52 changes: 52 additions & 0 deletions openspec/specs/memory-system/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## ADDED Requirements

### Requirement: Conversation Persistence
The system SHALL persist every conversation exchange (user input, LLM output, tool results) as a timestamped markdown file in the `memory/` directory.

#### Scenario: System persists a conversation message
- **WHEN** the user sends a message or the LLM generates a response
- **THEN** a new markdown file is written to `memory/` containing the exchange with YAML frontmatter for metadata (timestamp, provider, model)

#### Scenario: System reads conversation history from memory
- **WHEN** the user reopens the TUI after exiting
- **THEN** the system loads the latest conversation file from `memory/` and displays the history in the conversation panel

### Requirement: Tool Output and Execution Log Storage
The system SHALL store tool execution results and error logs as separate markdown files in `memory/` with cross-reference links to the parent conversation file.

#### Scenario: System stores a tool output
- **WHEN** a registered skill or tool executes
- **THEN** the result is written to a timestamped markdown file in `memory/tools/` with a reference link back to the conversation

#### Scenario: System stores an execution error
- **WHEN** a tool execution throws an unhandled error
- **THEN** the error (stack trace reduced) is written to `memory/errors/` as a markdown file

### Requirement: User-Provided Context Storage
The system SHALL allow users to write free-form context notes to `memory/` which are appended to the LLM context window at the start of each interaction.

#### Scenario: User adds a context note
- **WHEN** the user writes a context note via the TUI (`:context add <text>`)
- **THEN** a markdown file is created in `memory/context/` and appended to the conversation context prefix

#### Scenario: System loads context before interaction
- **WHEN** a new conversation message is sent
- **THEN** the system reads all recent context files from `memory/context/` and prepends them to the LLM prompt

### Requirement: Memory Indexing
The system SHALL maintain an index file (`memory/_index.md`) with YAML frontmatter that records the path, title, and timestamp of persisted memory entries for fast retrieval.

#### Scenario: Index is updated on new entry
- **WHEN** a new memory markdown file is created
- **THEN** the system appends an entry to `memory/_index.md` with the file path, timestamp, and a short title

#### Scenario: System searches memory by index
- **WHEN** the user runs `:memory search <query>` in the TUI
- **THEN** the system queries `memory/_index.md` frontmatter and opens the matching file

### Requirement: Memory Retention Policy
The system SHALL enforce a configurable retention policy that automatically purges memory files older than a specified duration or beyond a maximum entry count.

#### Scenario: Old memory files are purged
- **WHEN** a memory file exceeds the configured `retention.days` from `config.yaml`
- **THEN** the system removes the file during the next maintenance cycle
56 changes: 56 additions & 0 deletions openspec/specs/sandbox-rte/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
## ADDED Requirements

### Requirement: Process Isolation
The system SHALL execute all skill scripts in a forked Node.js process with constrained memory and CPU limits, preventing the sandboxed process from affecting the host environment.

#### Scenario: Skill executes in a forked process
- **WHEN** the harness invokes a registered skill
- **THEN** a new Node.js process is forked with `--max-old-space-size` and CPU cgroups applied as configured

#### Scenario: Child process is terminated on timeout
- **WHEN** a skill execution exceeds the configured `sandbox.timeout.seconds`
- **THEN** the forked process receives a SIGTERM and is killed with SIGKILL after a secondary grace period

### Requirement: Filesystem Access Control
The sandbox SHALL restrict file access to explicitly permitted paths. Skill scripts SHALL NOT be able to read or write outside the sandbox path and the mapped memory directory.

#### Scenario: Skill reads a permitted file
- **WHEN** a skill performs a filesystem read via `fs.readFile`
- **THEN** the system resolves the path and succeeds only if the resolved path falls within the allowed scope

#### Scenario: Skill attempts to access an unauthorized path
- **WHEN** a skill attempts to read a file outside its sandbox
- **THEN** the system intercepts the call and throws an `AccessDeniedError`

### Requirement: Network Access Control
The sandbox SHALL allow outbound network access only to URLs that match the allowlist defined in `config.yaml`. Schemes `file://`, `gopher://`, and `dict://` are always blocked.

#### Scenario: Skill makes an allowed network request
- **WHEN** a skill performs an HTTP request to a URL in the allowlist
- **THEN** the system permits the request and returns the response to the skill

#### Scenario: Skill attempts a disallowed request
- **WHEN** a skill attempts to connect to a URL not on the allowlist or using a blocked scheme
- **THEN** the system aborts the request and logs a `NetworkViolation` event to telemetry

### Requirement: Environment Variable Isolation
The sandbox SHALL inject only explicitly listed environment variables into the child process. Sensitive variables (e.g., `AUTH_API_KEY`, `OPENAI_API_KEY`) SHALL be injected from the harness config and NOT inherited from the host.

#### Scenario: Skill receives allowed environment variables
- **WHEN** a child process starts
- **THEN** only variables listed in `sandbox.env.allowlist` in `config.yaml` are injected

#### Scenario: Child process attempts to read host env vars
- **WHEN** a skill tries to access `process.env.UNKNOWN_VAR`
- **THEN** the variable is undefined as it was not injected

### Requirement: Capability Restriction via Permission Model
The sandbox SHALL enforce a capability model where each skill's granted permissions (`config.yaml` or skill metadata) determine what resources the child process can access.

#### Scenario: Skill with no permissions runs in minimal sandbox
- **WHEN** a skill declares zero permissions in its metadata
- **THEN** the child process receives read-only access to its own sandbox directory only

#### Scenario: Skill with network permission makes allowed request
- **WHEN** a skill declares `network:outbound` permission
- **THEN** the child process is allowed to make HTTP requests to allowlisted URLs
52 changes: 52 additions & 0 deletions openspec/specs/session-management/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## ADDED Requirements

### Requirement: Per-Session State Tracking
The system SHALL create a unique session identifier for each TUI invocation and track session-scoped state including the active LLM provider, conversation window size, and current skill context.

#### Scenario: Each TUI session gets a unique ID
- **WHEN** the user launches the harness
- **THEN** the system generates a UUID for the session and associates all memory, telemetry, and config mutations with that ID

#### Scenario: Active provider is tracked per session
- **WHEN** the user switches providers via `:provider set <name>`
- **THEN** the session state records the new provider and uses it for all subsequent LLM calls in that session

### Requirement: Session Context Window
The system SHALL maintain a conversation context window limited to the most recent N exchanges (configurable via `session.context_window_size` in `config.yaml`), discarding older exchanges from the active LLM prompt.

#### Scenario: Context window is enforced
- **WHEN** a conversation exceeds `session.context_window_size` exchanges
- **THEN** the system removes the oldest exchanges from the prompt sent to the LLM while retaining full history in memory

#### Scenario: Context window is configured
- **WHEN** `config.yaml` sets `session.context_window_size: 20`
- **THEN** only the last 20 message exchanges are included in the LLM prompt

### Requirement: Session Memory Loading
On session creation, the system SHALL load the latest conversation file from `memory/` and reconstruct the visible conversation buffer to provide continuity across sessions.

#### Scenario: Session resumes from last conversation
- **WHEN** the harness starts and a previous conversation file exists in `memory/`
- **THEN** the system renders previous messages in the conversation panel up to the context window limit

### Requirement: Runtime Config Mutation
The system SHALL allow session-scoped configuration to be modified at runtime via TUI commands or programmatic API, persisting changes to both in-memory state and `config.yaml` on disk.

#### Scenario: Session mutates memory retention
- **WHEN** the user types `:config set memory.retention.days 7` during an active session
- **THEN** the system updates the retention policy immediately and writes the change to `config.yaml`

#### Scenario: Session mutates skill permissions
- **WHEN** the user types `:config set skills.fs-read.permissions ["filesystem:read"]` during a session
- **THEN** the system updates the registered skill's permission scope for the remainder of the session

### Requirement: Session Shutdown and Cleanup
On session termination, the system SHALL flush all pending telemetry spans, close file handles on memory files, and write a final conversation state to `memory/`.

#### Scenario: Session flushes telemetry on exit
- **WHEN** the user exits the TUI (`Ctrl+C` or `:quit`)
- **THEN** the system signals the OpenTelemetry provider to export all pending spans and waits for completion

#### Scenario: Session writes final memory state
- **WHEN** the session terminates
- **THEN** the system appends any remaining unsaved conversation exchanges to the latest memory file
45 changes: 45 additions & 0 deletions openspec/specs/skills-registry/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
## ADDED Requirements

### Requirement: Skill Discovery
The system SHALL automatically discover all skills in the `skills/` directory by scanning for subdirectories and loading their metadata from a `skill.yaml` or `skill.json` file within each skill root.

#### Scenario: System discovers a new skill directory
- **WHEN** a new directory is added to `skills/` containing a valid `skill.yaml` file
- **THEN** the registry registers the skill and makes it available for invocation on the next discovery cycle

#### Scenario: System ignores invalid skill directories
- **WHEN** a directory in `skills/` lacks a `skill.yaml` or `skill.json` file
- **THEN** the system skips the directory and logs a warning at the debug level

### Requirement: Schema Validation
The system SHALL validate each skill's input schema against a zod-like schema definition before activation and reject skills with invalid or missing schemas.

#### Scenario: Skill with valid schema is activated
- **WHEN** a skill defines a valid `inputSchema` in its metadata
- **THEN** the system validates the schema at registry load time and activates the skill

#### Scenario: Skill with invalid schema is rejected
- **WHEN** a skill's `inputSchema` fails validation
- **THEN** the system skips activation of the skill and logs the validation error

### Requirement: Input/Output Contracts
Every registered skill SHALL expose a defined input schema, a defined output schema, and a clear execution context specifying resource access boundaries.

#### Scenario: Tool invocation passes validated input to skill
- **WHEN** the harness invokes a skill
- **THEN** the input is validated against the skill's input schema before execution

#### Scenario: Skill output conforms to declared output schema
- **WHEN** a skill completes execution
- **THEN** the output is validated against the skill's output schema and an error is raised if it does not match

### Requirement: Permission Scoping
Each registered skill SHALL declare the permission scopes it requires (e.g., `filesystem:read`, `network:outbound`, `process:spawn`), and the harness SHALL enforce these scopes during execution.

#### Scenario: Skill declares required permissions
- **WHEN** a skill's metadata specifies a `permissions` array
- **THEN** the system grants those scopes to the sandbox during execution

#### Scenario: Skill runs without declared permissions
- **WHEN** a skill does not declare permissions in its metadata
- **THEN** the system grants only the default read-only filesystem scope
Loading