Skip to content
Closed
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
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,52 @@ This coding harness is organized around six practical building blocks:
6. **Delegation and bounded subagents**
Scoped subtasks can be delegated to helper agents that inherit enough context to help (but operate within limits).

 
## Component 7: Agent Skills (CS440 addition)

7. **Agent skills**
Reusable, folder-based instructions. Each skill is a folder with a `SKILL.md`
(front matter `name`, `description`, `triggers`, optional `context` / `resources`
files to preload, + step-by-step instructions).
The agent discovers them, runs a similarity check on every prompt, and injects
the selected skill's instructions into the prompt. The model can also load a
skill mid-task with the `use_skill` tool, and the user can force one with
`/skill <name> <request>`.

```
skills/
├── pr-review/SKILL.md
└── vacuum-world-agent/ # Lab 5 skill
├── SKILL.md
└── reference.md
```

Skills are searched in `--skills-dir`, `<repo>/skills`, `<repo>/skill`,
`<cwd>/skills`, `<cwd>/skill`, and the `skills/` folder next to
`mini_coding_agent.py`.

**Similarity check:** TF-IDF cosine between the prompt and each skill's
name/description/triggers, plus a bonus when a trigger phrase appears verbatim:
`score = 0.6 * cosine + 0.4 * trigger_hit`. The best skill is used if
`score >= --skill-threshold` (default `0.20`).

**Logging:** every prompt, similarity check (all scores) and executed skill is
written to `.mini-coding-agent/logs/skills.jsonl` and echoed to stderr:

```
[skills] discovered 2 skill(s): pr-review, vacuum-world-agent
[skills] prompt: 'Solve Lab 4: implement the agent described in spec.md ...'
[skills] similarity (threshold 0.20): vacuum-world-agent=0.590, pr-review=0.011 -> selected: vacuum-world-agent
[skills] executing skill 'vacuum-world-agent' (trigger=auto, score=0.590) from .../skills/vacuum-world-agent/SKILL.md; loaded files: spec.md, Test/test.py, vacuum-world-agent/reference.md
```

Flags: `--skills-dir PATH` (repeatable), `--skill-threshold FLOAT`, `--no-skills`.
REPL: `/skills`, `/skill <name> <request>`.

The SDLC documents for this component are in [`sdlc/`](sdlc/)
(intent → spec → plan → tests → verification), and the Lab 4 run that used
the Lab 5 skill is recorded in [`lab4_skill_run/`](lab4_skill_run/).

&nbsp;
## Requirements

Expand Down
2 changes: 2 additions & 0 deletions lab4_skill_run/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Lab 4 - Vacuum World (Strategy 1)
Implement the agent described in spec.md. Tests live in Test/.
21 changes: 21 additions & 0 deletions lab4_skill_run/RUN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Lab 4 solved by the mini agent using the Lab 5 skill

This folder is the workspace after the final run (run 3) described in
[`../sdlc/05-verification.md`](../sdlc/05-verification.md).

| File | Origin |
|------|--------|
| `spec.md`, `Test/test.py` | copied from `Lab4-PersistentMemory-CS440` (input) |
| `README.md` | two-line workspace README (input) |
| `agent_strategy1.py` | **written by the mini agent** (qwen3.5:4b) |
| `logs/skills.jsonl` | skill component log: discover → prompt → similarity_check → skill_executed |
| `logs/console_stderr.txt` | `[skills]` console log lines |
| `logs/console_stdout.txt` | agent banner + final answer |
| `logs/session.json` | full transcript (user, skill activation, tool calls, final) |
| `logs/earlier_iterations/` | evidence from runs 1 and 2 |

Check it:

```bash
python -m pytest -q Test/test.py
```
132 changes: 132 additions & 0 deletions lab4_skill_run/Test/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import os
import sys

import pytest

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)

from agent_strategy1 import PersistentMemoryAgent

class DummyVacuumWorld:
"""Minimal environment matching the lab's percept contract."""

def __init__(self):
self.grid = {
(0, 0): "start",
(0, 1): 0,
(0, 2): 0,
(0, 3): 0,
(1, 0): 0,
(1, 1): "X",
(1, 2): 0,
(1, 3): 0,
(2, 0): 0,
(2, 1): 0,
(2, 2): 0,
(2, 3): 0,
(3, 0): 0,
(3, 1): -1,
(3, 2): 0,
(3, 3): 10,
}
self.obstacles = {(1, 1)}
self.dirty = {(0, 1), (2, 2), (3, 1)}
self.pos = (0, 0)

def percept(self):
pos = self.pos
cell = self.grid.get(pos, 0)
dirty = pos in self.dirty
neighbors = {}
for direction, delta in {
"UP": (-1, 0),
"DOWN": (1, 0),
"LEFT": (0, -1),
"RIGHT": (0, 1),
}.items():
next_row = pos[0] + delta[0]
next_col = pos[1] + delta[1]
coord = (next_row, next_col)
if next_row < 0 or next_row >= 4 or next_col < 0 or next_col >= 4:
neighbors[direction] = None
elif coord in self.obstacles:
neighbors[direction] = None
else:
neighbors[direction] = self.grid.get(coord, 0)
return {"pos": pos, "cell": cell, "dirty": dirty, "neighbors": neighbors}


def test_memory_schema_is_initialized():
agent = PersistentMemoryAgent(DummyVacuumWorld())

assert set(agent.memory) == {
"visited",
"obstacles",
"rewards",
"dirty_known",
"clean_known",
"last_pos",
"last_action",
"step_count",
"history",
}
assert agent.memory["visited"] == set()
assert agent.memory["obstacles"] == set()
assert agent.memory["rewards"] == {}
assert agent.memory["dirty_known"] == set()
assert agent.memory["clean_known"] == set()
assert agent.memory["last_pos"] is None
assert agent.memory["last_action"] is None
assert agent.memory["step_count"] == 0
assert agent.memory["history"] == []


def test_update_memory_records_obstacles_and_dirty_state():
env = DummyVacuumWorld()
env.pos = (1, 0)
agent = PersistentMemoryAgent(env)
percept = env.percept()

agent.update_memory(percept)

assert (1, 0) in agent.memory["visited"]
assert (1, 1) in agent.memory["obstacles"]
assert (1, 0) in agent.memory["clean_known"]
assert len(agent.memory["history"]) >= 1
assert agent.memory["step_count"] == 1


def test_agent_sucks_when_current_cell_is_dirty():
env = DummyVacuumWorld()
env.pos = (0, 1)
agent = PersistentMemoryAgent(env)
percept = env.percept()

action = agent.program(percept)

assert action == "SUCK"


def test_agent_avoids_known_obstacles_and_moves_legally():
env = DummyVacuumWorld()
env.pos = (0, 0)
agent = PersistentMemoryAgent(env)
percept = env.percept()
agent.update_memory(percept)

action = agent.program(percept)

assert action in {"DOWN", "RIGHT"}
assert action != "UP"
assert action != "LEFT"


def test_bfs_next_step_for_reachable_target():
env = DummyVacuumWorld()
agent = PersistentMemoryAgent(env)
agent.memory["obstacles"] = {(1, 1)}

assert agent._bfs_next_step((0, 0), (0, 3)) == "RIGHT"
assert agent._bfs_next_step((0, 0), (3, 0)) == "DOWN"
Loading