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
11 changes: 5 additions & 6 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
name: Build and publish Docker image

# Publishes ghcr.io/<owner>/goldilocks-agent (whichever repo this runs in --
# junwen94/goldilocks-agent and stfc/goldilocks-agent both use this same
# file) so `docker compose pull` (or a plain `docker pull`) gives a
# stfc/goldilocks-agent, or a fork under its own owner) so `docker compose pull` (or a plain `docker pull`) gives a
# colleague a working image with no local clone/build/toolchain needed --
# only `main` and version tags publish; other branches/PRs only run the
# `test` gate below.
Expand Down Expand Up @@ -73,10 +72,10 @@ jobs:
- id: meta
uses: docker/metadata-action@v5
with:
# Derived from the running repo, not hardcoded to junwen94's --
# this same workflow also runs on stfc/goldilocks-agent (which has
# its own GITHUB_TOKEN, scoped to ghcr.io/stfc/*, not junwen94/*)
# and a hardcoded owner would just fail to push there.
# Derived from the running repo, not hardcoded: a fork runs this
# same workflow with its own GITHUB_TOKEN, scoped to its own
# ghcr.io/<owner>/* namespace, where a hardcoded owner would just
# fail to push.
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
Expand Down
3 changes: 1 addition & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ Frontend lives in `app/` (Node/Vite/React, not `uv`) — `cd app && npm install`
(a form-only tool, no LLM) with its own repo/Docker image, even though
`app/` now embeds its published Workbench UI as an npm dependency behind
the DFT Workbench Tool's inline panel and full-page detail view alike (a
grid full-page, one card at a time as tabs inline) — see design doc §13 and
[goldilocks-agent#1](https://github.com/junwen94/goldilocks-agent/issues/1).
grid full-page, one card at a time as tabs inline) — see design doc §13.

## Code style

Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ submission).
src/goldilocks_agent/ Python package (LangGraph orchestration, local HTTP/SSE server, Tools)
app/ React/Vite frontend (chat + Tools panel; DFT Workbench's inline panel
and full-page detail both embed goldilocks-core/web's published UI --
see issue #1) -- not "web": see design doc §13
see design doc §13) -- not "web"
mlip-cli/ Own project (own pyproject.toml), just a `janus-core[mace]` dependency pin --
keeps torch/mace out of goldilocks-agent's own env; MLIP Playground shells
out to `janus` (janus-core's own CLI) inside it, see tools/mlip_playground/
Expand Down Expand Up @@ -106,11 +106,11 @@ no Python/Node toolchain -- `build: .` in `docker-compose.yml` is only a
local-dev fallback (`docker compose up --build` forces a rebuild from
source, which does need a real clone). Add
`GOLDILOCKS_AGENT_IMAGE_OWNER`/`GOLDILOCKS_AGENT_IMAGE_TAG` to that same
`.env` if you want `junwen94`'s build instead, or a specific commit's
`.env` if you want a fork's build instead, or a specific commit's
`sha-xxxxxxx` tag rather than `latest` (only useful for reproducing one
exact build -- for everyday use, `latest` already tracks the newest
successful publish automatically, no manual bumping needed). Both repos
publish from the same source, just to different `ghcr.io` namespaces. Once
successful publish automatically, no manual bumping needed). A fork
publishes the same source to its own `ghcr.io` namespace. Once
you've picked an engine, open <http://localhost:8080>.

What's on by default in this image, no extra config needed: chat, **Find in
Expand Down Expand Up @@ -272,7 +272,7 @@ full-page (the Tools panel's own expand-all-tools button, top right, then
DFT Workbench from the six-Tool grid) -- both render the exact same
embedded goldilocks-core Workbench UI, published as an npm package and
installed into `app/`'s dependencies (see
[issue #1](https://github.com/junwen94/goldilocks-agent/issues/1)): the
`docs/goldilocks-agent-design.md` §13): the
full-page view shows all its cards (Structure/Analysis/Advisors/Bundles)
side by side, the inline panel shows the same cards as tabs, one at a time.
The first time either is opened, goldilocks-agent auto-starts
Expand Down
22 changes: 11 additions & 11 deletions app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const MODEL_GROUPS: { group: string; items: ModelOption[] }[] = [
},
];

// Flattens MODEL_GROUPS to look up a persisted selection by id (#4 -- reload
// Flattens MODEL_GROUPS to look up a persisted selection by id (cc5f0dc -- reload
// restores it from storage, but the id alone can't tell us the display group,
// so this recovers the full model object). Skips `disabled` entries: they
// were never selectable in the first place, so a stale persisted id pointing
Expand All @@ -140,7 +140,7 @@ function findModelById(id) {
// in. Mirrors the live path's own filtering (drop `role: "tool"` entries and
// content-less assistant turns -- design doc 12.3: chat never shows raw
// tool args/results) but also recognizes a past `dft_download_bundle` result
// and re-inserts its `dft-bundle-ready` card (#5) -- the checkpointer has no
// and re-inserts its `dft-bundle-ready` card (cc5f0dc) -- the checkpointer has no
// concept of that card, only the raw tool call/result pair, and OpenAI's
// wire format puts the tool's *name* on the preceding assistant message's
// `tool_calls[]`, not on the tool-role reply itself, so the id needs to be
Expand Down Expand Up @@ -1861,7 +1861,7 @@ function coreWorkbenchStatusMessage(status: { status: string; detail: string | n
if (status?.status === "error") {
return status.detail ?? "Couldn't start goldilocks-core's backend -- see the server logs for details.";
}
// (#3) The first start -- ever, or after a core upgrade -- installs core's
// (cc5f0dc) The first start -- ever, or after a core upgrade -- installs core's
// dependencies and downloads its models and pseudopotentials before
// goldilocks-core can begin listening, which has taken over 20 minutes on a
// slow link (core_server.py's `_STARTUP_TIMEOUT`); a start with everything
Expand Down Expand Up @@ -1911,7 +1911,7 @@ export default function App() {
const [hoveredHandle, setHoveredHandle] = useState(null);
// One workspace/client instance per app lifetime -- recreating it every
// time DFT Workbench's detail page is opened would drop in-progress state
// (see junwen94/goldilocks-agent#1 -- this is the embedded
// (see design doc §13 -- this is the embedded
// goldilocks-workbench package DFT Workbench's full-page detail and inline
// panel both reuse, see CoreWorkbenchTree/CoreWorkbenchTabs).
const coreWorkspace = useMemo(() => createCoreWorkspace(new HttpCoreClient()), []);
Expand Down Expand Up @@ -1979,7 +1979,7 @@ export default function App() {
// (resolve_model()'s GOLDILOCKS_AGENT_MODEL env-var override always wins
// server-side) -- defaulting to Claude there would misrepresent which
// model actually answers. Elsewhere (non-shared), reload used to always
// reset this to Claude (#4) -- restore whatever was last picked instead,
// reset this to Claude (cc5f0dc) -- restore whatever was last picked instead,
// same browserStorage()/STORAGE_KEYS pattern as activeChatId.
const [selectedModel, setSelectedModel] = useState(() =>
SHARED_DEPLOYMENT
Expand Down Expand Up @@ -2240,7 +2240,7 @@ export default function App() {
loadConversationsOnce();
hydrateExperienceLevel();
} else {
// No conversation list to check against in shared mode (#9) -- the
// No conversation list to check against in shared mode (cc5f0dc) -- the
// persisted id is this tab's own `sessionStorage` entry (never
// another visitor's, browserStorage() already picked sessionStorage
// for SHARED_DEPLOYMENT), so it's safe to trust directly. Synthesize
Expand All @@ -2257,7 +2257,7 @@ export default function App() {
}
}, []);

// Restores which conversation was open before a reload (#9 -- previously
// Restores which conversation was open before a reload (cc5f0dc -- previously
// nothing did, so a refresh always landed on a blank "no chat selected"
// screen even though the conversation itself was still on the server).
// Local-mode only: waits for `loadConversationsOnce()` above to actually
Expand Down Expand Up @@ -2285,7 +2285,7 @@ export default function App() {
}, [selectedModel]);

// Persists `coreWorkspace`'s structure + calc settings on every real change
// (#5 -- previously a reload silently dropped all of it, since `coreWorkspace`
// (cc5f0dc -- previously a reload silently dropped all of it, since `coreWorkspace`
// itself is recreated empty by the `useMemo` above), with the vocabulary
// they're written in (see `restorableDraftPatch`). Only ever writes a loaded
// structure: the workspace starts empty on every page load, and clearing the
Expand All @@ -2310,10 +2310,10 @@ export default function App() {
}, [coreWorkspace]);

// Replays the persisted structure/settings back into `coreWorkspace` once,
// on mount (#5). A real re-run of inspect/review/preview against
// on mount (cc5f0dc). A real re-run of inspect/review/preview against
// goldilocks-core's backend, not a cheap local restore -- there's no
// "just set the state" action in goldilocks-workbench's `Workspace`
// interface, only real actions -- so this deliberately trades the #7
// interface, only real actions -- so this deliberately trades c7bfc57's
// no-silent-duplicate-real-execution principle for a seamless reload here
// (explicit call, 2026-09-28, after weighing that tradeoff against the
// cheaper alternatives). Best-effort: a structure goldilocks-core can no
Expand Down Expand Up @@ -3391,7 +3391,7 @@ export default function App() {
//
// The streaming assistant bubble is found by its `streamId` in the live
// `current.messages` on each delta, not rebuilt from a snapshot taken
// once at the start of the call (#6, 2026-09-28): a `tool_result`/
// once at the start of the call (cc5f0dc, 2026-09-28): a `tool_result`/
// `client_tool_call` handler earlier in this same stream may already have
// appended something (a `dft-bundle-ready` card, a confirmation card) via
// `current.messages` -- reconstructing from a frozen snapshot on every
Expand Down
11 changes: 5 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ services:
# as a fallback/dev override: `docker compose up --build` (or `poe`/
# local dev work) still builds from source and tags the result the same
# way, and if the registry is ever unreachable, `up` falls back to it.
# This one file is shared verbatim between junwen94/goldilocks-agent and
# stfc/goldilocks-agent (same commit, pushed to both) -- owner/tag are
# env-overridable rather than hardcoded so neither repo's default has to
# lose to the other's. Override via a sibling `.env` file or inline:
# `GOLDILOCKS_AGENT_IMAGE_OWNER=stfc GOLDILOCKS_AGENT_IMAGE_TAG=sha-xxxxxxx docker compose up -d`.
image: ghcr.io/${GOLDILOCKS_AGENT_IMAGE_OWNER:-junwen94}/goldilocks-agent:${GOLDILOCKS_AGENT_IMAGE_TAG:-latest}
# Defaults to the canonical stfc/goldilocks-agent image (public, pulls
# anonymously). A fork's CI publishes under its own owner, so owner/tag
# stay env-overridable -- via a sibling `.env` file or inline:
# `GOLDILOCKS_AGENT_IMAGE_OWNER=<fork-owner> GOLDILOCKS_AGENT_IMAGE_TAG=sha-xxxxxxx docker compose up -d`.
image: ghcr.io/${GOLDILOCKS_AGENT_IMAGE_OWNER:-stfc}/goldilocks-agent:${GOLDILOCKS_AGENT_IMAGE_TAG:-latest}
pull_policy: always
build: .
# `host.docker.internal`: reaches Ollama running *natively* on your own
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ git clone <this-repo> && cd goldilocks-agent
docker compose up
```

`docker compose up` pulls the `agent` image from `ghcr.io/junwen94/goldilocks-agent`
`docker compose up` pulls the `agent` image from `ghcr.io/stfc/goldilocks-agent`
(built and published automatically on every push to `main` -- see
`.github/workflows/docker-publish.yml`) instead of building it locally, so
this is fast even on a machine with no Python/Node toolchain at all.
Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ not built.
This site covers running and configuring goldilocks-agent. For the full
product design and a dated log of what's actually been built vs. still
planned, see
[`docs/goldilocks-agent-design.md`](https://github.com/junwen94/goldilocks-agent/blob/main/docs/goldilocks-agent-design.md)
[`docs/goldilocks-agent-design.md`](https://github.com/stfc/goldilocks-agent/blob/main/docs/goldilocks-agent-design.md)
and
[`docs/goldilocks-agent-implementation-plan.md`](https://github.com/junwen94/goldilocks-agent/blob/main/docs/goldilocks-agent-implementation-plan.md)
[`docs/goldilocks-agent-implementation-plan.md`](https://github.com/stfc/goldilocks-agent/blob/main/docs/goldilocks-agent-implementation-plan.md)
in the repository -- internal working documents, not part of this site.

## Layout
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ site_name: goldilocks-agent
site_description: >-
LLM orchestration, feedback-driven scientific workflow, and AiiDA execution
layer for goldilocks-core.
repo_url: https://github.com/junwen94/goldilocks-agent
repo_url: https://github.com/stfc/goldilocks-agent
repo_name: goldilocks-agent

# `docs/` also holds the internal, dated design log (Chinese-language,
Expand Down
8 changes: 4 additions & 4 deletions src/goldilocks_agent/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
Because LangGraph replays a node's whole function body from the top on
each resume, `call_tool` handles exactly *one* tool call per execution,
never the whole batch on `state["messages"][-1].tool_calls` in a Python
loop (2026-09-28, #7): a loop with two-or-more interrupt-gated calls in
loop (2026-09-28, fixed in c7bfc57): a loop with two-or-more interrupt-gated calls in
one execution replays every already-answered call's code -- including its
real dispatch (`await fn(**call["args"])`), not just its `writer()` status
-- every time a *later* call's interrupt resolves. For a real side effect
Expand Down Expand Up @@ -304,7 +304,7 @@ def _find_next_tool_call(state: MessagesState) -> dict | None:
matching ToolMessage in state yet, or None if every call on it is
already answered. Scans backward for the nearest AIMessage rather than
assuming `state["messages"][-1]` is it -- once `call_tool` starts
appending ToolMessages one at a time (#7), the tool-calling AIMessage
appending ToolMessages one at a time (c7bfc57), the tool-calling AIMessage
is no longer the last element."""
messages = state["messages"]
for i in range(len(messages) - 1, -1, -1):
Expand All @@ -325,7 +325,7 @@ def _find_next_tool_call(state: MessagesState) -> dict | None:
async def call_tool(state: MessagesState) -> dict:
"""Dispatch exactly one tool call -- the first unanswered one on the
most recent AIMessage (`_find_next_tool_call`) -- never the whole
batch in one execution (see the module docstring's #7 note on why:
batch in one execution (see the module docstring's c7bfc57 note on why:
LangGraph replays everything before an `interrupt()` on resume, so a
Python loop handling several interrupt-gated calls in one execution
re-runs earlier calls' real dispatch too, not just their status,
Expand Down Expand Up @@ -436,7 +436,7 @@ def route_after_llm(state: MessagesState) -> str:

def route_after_tool(state: MessagesState) -> str:
"""Loop back to `tool` while the most recent AIMessage still has an
unanswered tool call (#7 -- one call per `call_tool` execution, never
unanswered tool call (c7bfc57 -- one call per `call_tool` execution, never
the whole batch); once every call has a ToolMessage, hand control back
to `llm` to see the full result set."""
if _find_next_tool_call(state) is not None:
Expand Down
Loading